Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

swiftUI: Changing @State in onAppear crashes

Tags:

swift

swiftui

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?

like image 434
mica Avatar asked Mar 12 '20 22:03

mica


People also ask

How to safely update the view state in SwiftUI?

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. ...

What is state in SwiftUI?

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.

Does SwiftUI need to be re-computed every time?

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.

How to update the view state without getting runtime error?

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.


1 Answers

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?

like image 74
user3441734 Avatar answered Sep 28 '22 18:09

user3441734