skip to Main Content

I’m following the example of "Thinking in Swift UI" book.

I’m creating an Observable Object

final class Contact: ObservableObject, Identifiable {
    let id = UUID()
    @Published var name: String
    @Published var city: String
    @Published var profile: String
    
    init(name: String, city: String, profile: String) {
        self.name = name
        self.city = city
        self.profile = profile
    }
}

Content View is defined as list of Button which selects the contact. And it stores the contacts as normal array property

struct ContentView: View {
    @State var selection: Contact?
    var contacts: [Contact]
    
    var body: some View {
        HStack {
            ForEach(contacts) { contact in
                Button(contact.name) {
                    self.selection = contact
                }
            }
        }
        if let c = selection {
            Detail2(contact: c)
        }
    }
}

And here we have Detail View which calls the id modifier to change the identify of the view and supposedly make "onAppear" be called again – which doesn’t work

struct Detail: View {
    @ObservedObject var contact: Contact
    @StateObject var loader = ImageLoader()
    var body: some View {
        HStack {
            loader.imageView
            VStack {
                Text(contact.name).bold()
                Text(contact.city)
            }
        }
        .id(contact.id)
        .onAppear {
            loader.load(image: contact.profile)
        }
    }
}

And here I mocked the Loader

class ImageLoader: ObservableObject {
    
    var image: String = ""
    init() {
        print("Initializer of Image Loader was called")
    }
    
    func load(image: String) {
        print("Brrr.... loading image")
        print("The image is: (image)")
        self.image = image
    }
}

The app is defined as Content View which passes the array of contacts:

@main
struct SwiftUILearning2App: App {
    
    var body: some Scene {
        WindowGroup {
            ContentView(contacts: [ Contact(name: "name1", city: "city1", profile: "profile_1"),
                                   Contact(name: "name2", city: "city2", profile: "profile_2")])
        }
    }
}

Why doesn’t onAppear – and in effect load.loader( ) is not called when the new contact is assigned?
According to the book .id(contact.id) modifier called on Detail View should be able to make onAppear to be called again.

After the example without the .id(contact.id modifier the book states:

"Upon first try, this seems to work, but when we change the contact object (for example, by selecting a different person) the view never reloads the image. The simplest way to solve this problem is by telling SwiftUI that it should change the identity of the view:"

Here is a link to full code:
https://gist.github.com/pbrewczynski/63f2dfdba96dec2efcf7d81d304622f6

2

Answers


  1. In according with the swiftUI documentation:

    When the proxy value specified by the id parameter changes, the identity of the view — for example, its state — is reset.

    The view doesn’t call onAppear() method because it’s not created again, change its state and parameters.
    My suggestion is to call onChange() method:

        .onChange(of: contact) { newValue in
            loader.load(image: newValue.profile)
        }
    

    There are a lot of solutions, but id modifier is usually used for:

    1. Resetting State Values
    2. Triggering Transitions
    3. Improving the List View Performance

    An article here i hope is helpful

    Login or Signup to reply.
  2. A better way to do this is using .task(id:), e.g.

    .task(id: contact.id) {
        downloads = await Contacts.download()
    }
    

    It’s called on appear and when contact.id changes. But the best thing is the async task is automatically cancelled if it hasn’t finished before the contact changes or it disappears.

    If you just want to display then info then store the downloads in @State. If you want to persist it then you could set it on a @StateObject that does it later.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search