web-dev-qa-db-fra.com

Rendu SwiftUI View en tant que UIImage

J'essaie de rendre la vue SwiftUI en tant que UIImage, puis de laisser l'utilisateur choisir d'enregistrer sur la pellicule ou de l'envoyer à d'autres par e-mail.

À titre d'exemple, je veux rendre une liste de 50 lignes dans un UIImage.

struct MyList: View {
    var body: some View {
        List {
            ForEach(0 ..< 50, id:\.self) {
                Text("row \($0)")
            }
        }
    }
}

J'ai cherché sur Internet au cours des dernières semaines sans succès. J'ai essayé 2 approches différentes.

1. UIHostingController ( source ici )

let hosting = UIHostingController(rootView: Text("TEST"))
hosting.view.frame = // Calculate the content size here //
let snapshot = hosting.view.snapshot        // output: an empty snapshot of the size
print(hosting.view.subviews.count)          // output: 0

J'ai essayé layoutSubviews(), setNeedsLayout(), layoutIfNeeded(), loadView(), mais tous ont toujours donné 0 sous-vues.

2. UIWindow.rootViewController ( source ici )

var vc = UIApplication.shared.windows[0].rootViewController
vc = vc.visibleController          // loop through the view controller stack to find the top most view controller
let snapshot = vc.view.snapshot    // output: a snapshot of the top most view controller

Cela donne presque la sortie que je voulais. Cependant, l'instantané que j'obtiens est littéralement une capture d'écran, c'est-à-dire avec une barre de navigation, une barre d'onglets et une taille fixe (identique à la taille de l'écran). Ce que je dois capturer est simplement le contenu de la vue sans ces barres, et peut parfois être plus grand que l'écran (ma vue est une longue liste dans cet exemple).

J'ai essayé d'interroger vc.view.subviews Pour rechercher la vue de table que je veux, mais en renvoyant une [<_TtGC7SwiftUI16PlatformViewHostGVS_42PlatformViewControllerRepresentableAdaptorGVS_16BridgedSplitViewVVS_22_VariadicView_Children7ElementGVS_5GroupGVS_19_ConditionalContentS4_GVS_17_UnaryViewAdaptorVS_9EmptyView______: 0x7fb061cc4770; frame = (0 0; 414 842); anchorPoint = (0, 0); tintColor = UIExtendedSRGBColorSpace 0 0.478431 1 1; layer = <CALayer: 0x600002d8caa0>>] inutile.

Toute aide est très appréciée.

5
Anthony

Je me demande pourquoi les gens disent que votre question n'est pas claire. En fait, c'est parfaitement logique et c'est l'une des choses que je cherchais à faire moi-même.

J'ai finalement trouvé un moyen de faire les choses de la même manière que vous le souhaitez, c'est compliqué mais ça marche.

L'idée est d'abuser de votre deuxième possibilité (UIWindow.rootViewController). Celui-là est amusant parce que vous prenez votre fenêtre, vous découvrez ce que vous souhaitez dessiner et vous le dessinez.

Mais le problème est que c'est votre fenêtre principale, et vous demandez de dessiner votre fenêtre principale.

J'ai finalement réussi à le faire fonctionner en faisant un UIViewControllerRepresentable, ce qui me donne un UIViewController dans mon application SwiftUI. Ensuite, à l'intérieur, j'ai mis un seul UIHostingController, ce qui me donne une vue SwiftUI à l'intérieur de l'UIView. En d'autres termes, je fais un pont.

Dans la fonction update, je peux ensuite appeler une répartition asynchrone à view.layer.render(context) pour dessiner l'image.

Fait intéressant, le UIViewControllerRepresentable sera en fait plein écran. Je recommande donc de faire une .scale(...) pour que toute votre liste tienne dans l'écran (la commande de rendu ne se souciera pas qu'elle soit plus petite) et elle sera centrée. Donc, si vous connaissez la taille de votre image à l'avance, vous pouvez faire une HStack { VStack { YourStuff Spacer() } Spacer() } pour qu'elle soit rendue en haut à gauche.

Bonne chance!

1
Michel Donais