I want to set a @State Variable if a View appears by calling a Service.
Calling the Service in onAppear
crashes the app.
Calling the Service in a buttons action works fine.
struct ContentView: View {
@State var dirContent : [String] = []
var body: some View {
VStack {
List {
ForEach(dirContent, id: \.self){
fileName in
Text(fileName)
}
}
Button("Get Data"){self.dirContent = Service.getData()}
}
.onAppear{
//self.dirContent = Service.getData2() // Crashes!!
}
}
}
class Service{
static func getData()->[String]{
// ... get Data from somewhere
return ["line1", "line2"]
}
static func getData2()->[String]{
// ... get Data from somewhere
return ["line4", "line5"]
}
}
What's going wrong?
Safely Updating The View State 1 Updating the State View. When SwiftUI is computing the body of a view, the state should remain unchanged. ... 2 Breaking The Loop. In the following example, we have a geometry effect that rotates an image. ... 3 Unexpected Loops. So far, things are pretty clear. ... 4 One More Thing. ... 5 In Summary. ...
State is a persistent storage created by SwiftUI on your view’s behalf. SwiftUI can destroy and recreate your view whenever needed without losing the state that the property was storing. When the state value changes, the view invalidates its appearance and recomputes the body. Use the state as the single source of truth for a given view.
As you can see, SwiftUI is wise enough to know the body does not need to be re-computed every time, only when the state really changed. That means that unless you set a different value in the state, the view will not get invalidated. In the case above: only when the cardinal direction is different it will request a new body.
Some other places where you can update the view state without getting the runtime error are: onAppear, onDisappear, onPreferenceChange, onEnded, etc. At first glance, it seems the counter variable is updated once every time the body of the view is computed.
the see why this happens, try to modify your code
var body: some View {
VStack {
List {
ForEach(dirContent, id: \.self){
fileName in
Text(fileName).onAppear {
print("item appear")
}
}
}.onAppear {
print("list appear")
}
Button("Get Data"){self.dirContent = Service.getData()}
}
.onAppear{
print("content appear")
DispatchQueue.main.async {
self.dirContent = Service.getData2()
}
}
}
the "old school" debug print shows us
content appear
list appear
item appear
item appear
Now it is clear, isn't it?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With