Tentant de charger une image après le chargement de la vue, l'objet modèle pilotant la vue (voir MovieDetail ci-dessous) a une urlString. Parce qu'un élément SwiftUI View
n'a pas de méthode de cycle de vie (et qu'il n'y a pas de contrôleur de vue pilotant les choses), quelle est la meilleure façon de gérer cela?
Le principal problème que j'ai est quelle que soit la manière dont j'essaie de résoudre le problème (liaison d'un objet ou utilisation d'une variable State), ma vue n'a pas l' urlString
avant son chargement ...
// detail view that needs to make the asynchronous call struct MovieDetail : View { let movie: Movie @State var imageObject = BoundImageObject() var body: some View { HStack(alignment: .top) { VStack { Image(uiImage: imageObject.image) .scaledToFit() Text(movie.title) .font(.subheadline) } } } }
// root content list view that navigates to the detail view struct ContentView : View { var movies: [Movie] var body: some View { NavigationView { List(movies) { movie in NavigationButton(destination: MovieDetail(movie: movie)) { MovieRow(movie: movie) } } .navigationBarTitle(Text("Star Wars Movies")) } } }
// movie object struct Movie: Decodable, Identifiable { let id: String let title: String let year: String let type: String var posterUrl: String private enum CodingKeys: String, CodingKey { case id = "imdbID" case title = "Title" case year = "Year" case type = "Type" case posterUrl = "Poster" } }
Merci d'avance.
3 Réponses :
J'espère que ceci est utile. J'ai trouvé un article de blog qui parle de faire des choses surAppear pour une vue de navigation.
L'idée serait que vous intégriez votre service dans un objet BindableObject et que vous vous abonniez à ces mises à jour dans votre vue.
import SwiftUI import Combine class ReposStore: BindableObject { var repos: [Repo] = [] { didSet { didChange.send(self) } } var didChange = PassthroughSubject<ReposStore, Never>() let service: GithubService init(service: GithubService) { self.service = service } func fetch(matching query: String) { service.search(matching: query) { [weak self] result in DispatchQueue.main.async { switch result { case .success(let repos): self?.repos = repos case .failure: self?.repos = [] } } } } }
struct SearchView : View { @State private var query: String = "Swift" @EnvironmentObject var repoStore: ReposStore var body: some View { NavigationView { List { TextField($query, placeholder: Text("type something..."), onCommit: fetch) ForEach(repoStore.repos) { repo in RepoRow(repo: repo) } }.navigationBarTitle(Text("Search")) }.onAppear(perform: fetch) } private func fetch() { repoStore.fetch(matching: query) } }
Crédit à: Majid Jabrayilov
Corrigez-moi si je me trompe, mais l'utilisation de la fetch
dans onAppear
provoque une requête réseau à chaque fois que la vue apparaît. (par exemple dans un TabView).
Entièrement mis à jour pour Xcode 11.2, Swift 5.0
Je pense que le viewDidLoad()
juste égal à implémenter dans la fermeture du corps.
SwiftUI nous donne des équivalents à viewDidAppear viewDidAppear()
et viewDidDisappear()
sous la forme de onAppear()
et onDisappear()
. Vous pouvez attacher n'importe quel code à ces deux événements que vous voulez, et SwiftUI les exécutera lorsqu'ils se produiront.
À titre d'exemple, cela crée deux vues qui utilisent onAppear()
et onDisappear()
pour imprimer des messages, avec un lien de navigation pour se déplacer entre les deux:
struct ContentView: View { var body: some View { NavigationView { VStack { NavigationLink(destination: DetailView()) { Text("Hello World") } } }.onAppear { print("ContentView appeared!") }.onDisappear { print("ContentView disappeared!") } } }
Cette solution fonctionne UNIQUEMENT si la structure de la vue n'est pas modifiée (vous n'ajoutez / supprimez pas de sous-vues). Cependant, vous pouvez toujours changer l'état des vues (comme la position, les animer).
Créer l'extension View
:
struct SomeView: View { var body: some View { VStack { Text("HELLO!") }.onLoad { print("onLoad") } } }
Ensuite, utilisez-le comme ceci:
extension View { func onLoad(perform action: (() -> Void)? = nil) -> some View { var actionPerformed = false return self.onAppear { guard !actionPerformed else { return } actionPerformed = true action?() } } }
Je n'ai pas trop testé ma solution, mais elle semble fonctionner correctement. «onLoad» ne sera imprimé qu'une seule fois même si vous revenez d'une autre vue dans la pile de navigation.
pourquoi ça marche? Je pense que l'actionPerformed serait écrasée à chaque fois que la vue se recharge?
D'après ce que je comprends, SwiftUI ne recrée pas chaque vue à chaque fois que quelque chose change. Seules les vues affectées sont mises à jour / créées de nouveau. Donc, si nous appliquons onLoad
à la vue racine, cela fonctionnera. Mais comme je l'ai dit - je n'ai pas trop testé cela.
Ok, fait quelques tests supplémentaires. Cette approche ne fonctionne que si vous n'ajoutez / supprimez pas de vues. Vous pouvez toujours modifier l'état des vues (comme les déplacer, les animer).