@StateObject for view owning "viewModel" to use with @Observable observation framework object

I have an issue with a View that owns @Observable object (Instantize it, and is owner during the existence) in the same notion as @StateObject, however when @State is used the viewModel is re-created and deallocated on every view redraw.

In the previous models, @StateObject were used when the View is owner of the object (Owner of the viewModel as example)

When @Observable object is used in the same notion, @State var viewModel = ViewModel() the viewModel instance is recreated on views redraws.

@StateObject was maintaining the same instance during the View existence, that however is not happening when used @Observable with @State.

Post not yet marked as solved Up vote post of jendakub Down vote post of jendakub
728 views

Replies

You can see the effect in following code.

There are two viewModels, one using ObservableObject and second using @Observable observation framework.

The tabView content views owns the viewModel, however when the parent TabView is redrawed, the ownership of @Observation is teared down and new object is recreated on every redraw of TabView

@StateObject with ObservableObject maintains its view ownership and the object is not deallocated or recreated as would be expected.

@State with @Observable do not follow the ownership, and new object is created every time the parent is redrawed.

Following is the code:


import Observation

import SwiftUI



/// ObservableObject, via @StateObject.

class ViewModelObservable: ObservableObject {

    @Published public var title: String



    deinit {

        print("deinit ViewModelObservable")

    }



    init(title: String) {

        self.title = title

        print("init ViewModelObservable")

    }

}



/// New Observation framework object.

@Observable

class ViewModelObseration {

    public var title: String



    deinit {

        print("deinit ViewModelObseration")

    }



    init(title: String) {

        self.title = title

        print("init ViewModelObseration")

    }

}



struct SecondTabContentView: View {

    /// View Owns object (Observation Framework)

    @State var viewModelObservation: ViewModelObseration



    /// View Owns object (Observable Object)

    @StateObject var viewModelObservable: ViewModelObservable



    init() {

        _viewModelObservable = StateObject(wrappedValue: ViewModelObservable(title: "SecondTabContentView Observable"))

        _viewModelObservation = State(wrappedValue: ViewModelObseration(title: "SecondTabContentView Observation"))

    }



    var body: some View {

        VStack {

            Text(viewModelObservable.title)

            Text(viewModelObservation.title)

        }

    }

}



struct FirstTabContentView: View {

    /// View Owns object (Observation Framework)

    @State var viewModelObservation: ViewModelObseration



    /// View Owns object (Observable Object)

    @StateObject var viewModelObservable: ViewModelObservable



    init() {

        _viewModelObservable = StateObject(wrappedValue: ViewModelObservable(title: "FirstTabContentView Observable"))

        _viewModelObservation = State(wrappedValue: ViewModelObseration(title: "FirstTabContentView Observation"))

    }



    var body: some View {

        VStack {

            Text(viewModelObservable.title)

            Text(viewModelObservation.title)

        }

    }

}



struct ContentView: View {

    @State var tabSelection: Int = 1

    @State var redrawTrigger: Bool = false

    var body: some View {

        TabView(selection: $tabSelection) {

            FirstTabContentView()

                .tag(0)

                .tabItem { Label("First \(redrawTrigger ? "true" : "false") ", systemImage: "pen") }

            SecondTabContentView()

                .tag(1)

                .tabItem { Label("Second \(redrawTrigger ? "true" : "false")", systemImage: "pen") }

        }

        .task {

            try? await Task.sleep(for: .seconds(3))

            self.redrawTrigger = true

            try? await Task.sleep(for: .seconds(3))

            self.redrawTrigger = false

        }

    }

}



#Preview {

    ContentView()

}