张黒猫
张黒猫

Reputation: 61

What is the difference between @StateObject and @ObservedObject in child views in swiftUI

I created a Model like this:

class TestModel: ObservableObject {
    @Published var num: Int = 0
}

Model is be used in "Home" view and "Home"s child view "HomeSub"

struct Home: View {
    
    @StateObject var model = TestModel()
    
    var body: some View {
        NavigationView(content: {
            NavigationLink(destination: HomeSub(model: model)) { Text("\(model.num)") }
        })
    }
}
struct HomeSub: View {
   //1
    @StateObject var model = TestModel()
   //2
    @ObservedObject var model = TestModel()

    var body: some View {
        VStack {
            Text("\(model.num)")
                .padding()
                .background(Color.red)
            Button("Add") {
                model.num += 1
            }
        }
        .onChange(of: model.num, perform: { value in
            print("homeSub: \(value)")
        })
        
    }
}

In HomeSub view, what is the difference between 1 and 2? When I run the project, they have exactly the same behavior.

Upvotes: 3

Views: 1583

Answers (3)

Petr Holub
Petr Holub

Reputation: 85

In this case it is better to use @ObservedObject as you are injecting the value from the parent. @StateObject is designed to hold an object independently on the View (it's lifecycle is managed by SwiftUI). It is not needed here because of the injection.

Upvotes: 0

lorem ipsum
lorem ipsum

Reputation: 29329

They are used almost interchangeably and in your setup create 2 instances of the same Model.

@StateObject's lifecycle is managed by SwiftUI, it is only available in iOS 14+.

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

@ObservableObject's lifecycle is managed by the developer (Can sometimes be reinitialized unintentionally with a View refresh because it is supposed to come from a Parent View), it is available in iOS 13+.

https://developer.apple.com/documentation/swiftui/managing-model-data-in-your-app

Upvotes: 0

vacawama
vacawama

Reputation: 154613

As you've written it, both @StateObject and @ObservedObject are doing the same thing in the child view. But, neither is correct because they are unnecessarily creating a new TestModel just to toss it and replace it with the one being passed in.

The correct way to write the child view is:

@ObservedObject var model: TestModel

In this case, no initial value is assigned to model in the child view, which means the caller will have to provide it. This is exactly what you want. One source of truth which is the model in the parent view.

Also, state variables (both @State and @StateObject) should be private to a view and should always be marked with private. If you had done this:

@StateObject private var model = TestModel()

in the child view, then you wouldn't have been able to pass the model from the parent view and you would have seen that only @ObservedObject can be used in this case.


Upon further testing, it seems that Swift/SwiftUI avoids creating the TestModel in the child view when it is written as @ObservedObject var model = TestModel(), but that syntax is still misleading to the reader and it should still be written as @ObservedObject var model: TestModel because that makes it clear that model is being initialized from somewhere else (that is, from the parent view).

Upvotes: 6

Related Questions