Search code examples
iosswiftswiftuiobservableobject

SwiftUI view getting reinitialized on autorotation


I have the following SwiftUI code:

struct TestContentView: View {
    @Environment(\.verticalSizeClass) var verticalSizeClass
    @Environment(\.horizontalSizeClass) var horizontalSizeClass
    
    var body: some View {
        TestView()
            .ignoresSafeArea()
    }
}

struct TestView: View { 
    @State var model:TestModel = TestModel()
    
    var body: some View {
        Text("Count \(model.counter)")
    }
}

@Observable
class TestModel {
    var counter:Int = 0
    
    deinit {
        print("Calling deinit on TestModel")
    }
}

My problem is that TestView and TestModel are getting reinitialized on device autorotation, despite TestModel being a @State variable. This is not I want and I understand issue is I have declared size class environment variables that is causing TestView to be reset to new value. One way to solve the problem is by moving the testModel to superview and initializing it, but I am not sure that is always a scalable approach if you have deeper view hierarchies. What is the right approach for solving this issue, especially if one can have horizontalSizeClass or verticalSizeClass deep up in the view hierarchy?


Solution

  • Some Environment properties make the View re-instantiate. Developers don’t have any control over that so the solution is to use optional.

    A State property always instantiates its default value when SwiftUI instantiates the view.

    Instead, you can defer the creation of the object using the task(priority:_:) modifier, which is called only once when the view first appears

    struct ContentView: View {
        @State private var library: Library?
    
        var body: some View {
            switch library {
            case .some(let library):
                //@Bindable var library = library
                LibraryView(library: library)
            case .none:
                ProgressView() //Won’t ever be visible since it happens so fast.
                    .task {
                        self.library = Library()
                    }
             }
        }
    }
    

    Delaying the creation of the observable state object ensures that unnecessary allocations of the object doesn’t happen each time SwiftUI initializes the view. Using the task(priority:_:) modifier is also an effective way to defer any other kind of work required to create the initial state of the view, such as network calls or file access.

    https://developer.apple.com/documentation/swiftui/state