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
In according with the swiftUI documentation:
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:
There are a lot of solutions, but id modifier is usually used for:
An article here i hope is helpful
A better way to do this is using
.task(id:)
, e.g.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.