import SwiftUI
class Counter: ObservableObject {
@Published var count: Int = 0
func increment() {
count += 1
}
}
struct ContentView: View {
@StateObject private var counter : Counter = Counter()
var body: some View {
VStack {
Text("Parent Counter: \(counter.count)")
Button("Increment Child Counter") {
counter.increment()
}
ChildView(counter: counter)
}
}
}
struct ChildView: View {
@StateObject var counter: Counter
var body: some View {
VStack {
Text("Child Counter: \(counter.count)")
Button("Increment Child Counter") {
counter.increment()
}
}
}
}
struct RandomNumberView: View {
@State var randomNumber = 0
var body: some View {
VStack {
Text("Random number is: \(randomNumber)")
Button("Randomize number") {
randomNumber = (0..<1000).randomElement()!
}
}.padding(.bottom)
ContentView()
}
}
#Preview {
RandomNumberView()
}
import SwiftUI
class Counter: ObservableObject {
@Published var count: Int = 0
func increment() {
count += 1
}
}
struct ContentView: View {
@StateObject private var counter : Counter = Counter()
var body: some View {
VStack {
Text("Parent Counter: \(counter.count)")
Button("Increment Child Counter") {
counter.increment()
}
ChildView(counter: counter)
}
}
}
struct ChildView: View {
@ObservedObject var counter: Counter
var body: some View {
VStack {
Text("Child Counter: \(counter.count)")
Button("Increment Child Counter") {
counter.increment()
}
}
}
}
struct RandomNumberView: View {
@State var randomNumber = 0
var body: some View {
VStack {
Text("Random number is: \(randomNumber)")
Button("Randomize number") {
randomNumber = (0..<1000).randomElement()!
}
}.padding(.bottom)
ContentView()
}
}
#Preview {
RandomNumberView()
}
Both Having the same Output View.
I have checked many of the articles and my own cases in @StateObject vs @ObservedObject in childView But I don't find anything useful. So what's the difference in this case. I want the explanation for this to code how it works when @StateObject & @ObservedObject used in childView.
When SwiftUI updates a View
that has already been seen before, it restores the values of that view's @State
and @StateObject
properties to the values they had on the prior rendering.
So, the first time SwiftUI asks ContentView
for its body
, ContentView
creates a ChildView
and passes its (ContentView
's) counter
to initialize ChildView
's counter
.
When counter
's @Published
count
property changes, SwiftUI asks ContentView
for its body
again. ContentView.body
creates another ChildView
and again passes its counter
down to initialize ChildView
's counter
.
However, SwiftUI recognizes that this ChildView
represents an update to the prior ChildView
. So it throws away whatever was stored in this new ChildView
's counter
and sets it to the Counter
that was used the first time it saw this ChildView
.
This doesn't make a difference in your example, because you only ever create one Counter
.
Let's write a example where it does matter.
First, here's a simple Button
that displays a count and increments the count when tapped, just using a Binding
:
struct CountButton: View {
@Binding var count: Int
var body: some View {
Button {
count += 1
} label: {
Text("\(count)")
.frame(width: 60)
}
}
}
Now here's a view that uses a StateObject
to hold a Counter
and pass its count
to a CountButton
:
struct ViewWithStateObject: View {
@StateObject var counter: Counter
var body: some View {
GroupBox("View with StateObject") {
CountButton(count: $counter.count)
.frame(width: 300)
.padding()
}
.padding()
}
}
And here's a similar view that uses an ObservedObject
instead:
struct ViewWithObservedObject: View {
@ObservedObject var counter: Counter
var body: some View {
GroupBox("View with ObservedObject") {
CountButton(count: $counter.count)
.frame(width: 300)
.padding()
}
.padding()
}
}
And finally, here's a view that holds two Counter
instances, each in a StateObject
. It embeds both of the above views, and lets you choose which of its Counter
instances to pass to both children:
struct ContentView: View {
@StateObject private var counter0: Counter = Counter()
@StateObject private var counter1: Counter = Counter()
@State var useCounter1 = false
var body: some View {
VStack {
GroupBox("ContentView") {
VStack {
HStack {
CountButton(count: $counter0.count)
CountButton(count: $counter1.count)
}
Toggle("Pass counter1 to children", isOn: $useCounter1)
}
.frame(width: 300)
.padding()
}
ViewWithStateObject(counter: useCounter1 ? counter1 : counter0)
ViewWithObservedObject(counter: useCounter1 ? counter1 : counter0)
}
.padding()
}
}
If you tap the top left CountButton
in this example, you'll find that both children are updated as the count increases. And the buttons in the children also increment the same count. They are all using counter0
.
If you tap the top right CountButton
, only it updates. The other buttons are not affected, because only that top right button uses counter1
.
Next, tap the toggle. This makes ContentView
pass counter1
to the children.
Now, when you tap the top left button, both the top left button and the “View with StateObject” show the increment, but the “View with ObservedObject” button doesn't change. And if you tap the top right button, both it and the “View with ObservedObject” button show the increment, but the “View with StateObject” button doesn't change.
When you turn on the toggle, ContentView
passes counter1
to the children, instead of passing counter0
. But, before SwiftUI asks ViewWithStateObject
for its body
, SwiftUI discards the contents of ViewWithStateObject
's counter
property and replaces it with the prior contents. So it discards the new reference to counter1
and restores the old reference to counter0
. ViewWithStateObject
will never switch to counter1
.