iosswiftswiftuiswiftui-view

SwiftUI View - viewDidLoad()?


Trying to load an image after the view loads, the model object driving the view (see MovieDetail below) has a urlString. Because a SwiftUI View element has no life cycle methods (and there's not a view controller driving things) what is the best way to handle this?

The main issue I'm having is no matter which way I try to solve the problem (Binding an object or using a State variable), my View doesn't have the urlString until after it loads...

// 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"
    }
}
// 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"))
        }
    }
}
// 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)
            }
        }
    }
}

Solution

  • I hope this is helpful. I found a blogpost that talks about doing stuff onAppear for a navigation view.

    Idea would be that you bake your service into a BindableObject and subscribe to those updates in your view.

    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)
        }
    }
    
    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 = []
                    }
                }
            }
        }
    }
    

    Credit to: Majid Jabrayilov