Reputation: 771
I've noticed a scenario in SwiftUI
where if I update a Binding
and a State
variable at the same time, the View
is recomputed in 2 steps:
I've prepared a simple and dummy DEMO to reproduce this:
import SwiftUI
struct OtherView: View {
let size: CGSize
@Binding var x1: Int
@State var x2: Int = 0
var body: some View {
Text(text())
.frame(width: size.width,
height: size.height / 2)
.background(Color.blue)
.onTapGesture {
print("*** TAP ***")
withAnimation(Animation.easeIn(duration: 4)) {
x1 += 1
x2 += 1
}
}
}
func text() -> String {
print("x1: \(x1) + x2 = \(x2) = \(x1 + x2)")
return "x1: \(x1) + x2 = \(x2) = \(x1 + x2)"
}
}
struct ContentView: View {
@State private var value: Int = 0
var body: some View {
NavigationView {
GeometryReader { proxy in
OtherView(size: proxy.size,
x1: $value)
}
}
}
}
This snippet prints:
x1: 0 + x2 = 0 = 0
x1: 0 + x2 = 0 = 0
*** TAP ***
x1: 1 + x2 = 0 = 1
x1: 1 + x2 = 1 = 2
Which is not what I expected. If I remove the NavigationView
or move GeometryReader
from ContentView
to OtherView
, the same example would print:
x1: 0 + x2 = 0 = 0
*** TAP ***
x1: 1 + x2 = 1 = 2
As expected. I've also noticed that by changing the execution order in withAnimation
block I can get the State
to update before the Binding
.
Am I missing something? Why does the code trigger two updates?
Upvotes: 1
Views: 424
Reputation: 29242
struct
s are immutable when you update a variable that has a SwiftUI wrapper you are telling it so reload the entire View
/struct
. So, when you update x1
it triggers a reload and when you update x2
you trigger another.
If you want to control when the View
does a reload you have to use variables that are mutable/live in a class
, and are not wrapped with a SwiftUI wrapper.
struct OtherView: View {
let size: CGSize
@EnvironmentObject var vm: SyncUpdateViewModel
//@Binding var x1: Int
@State var x2: Int = 0
var body: some View {
Text(text())
.frame(width: size.width,
height: size.height / 2)
.background(Color.blue)
.onTapGesture {
print("*** TAP ***")
withAnimation(Animation.easeIn(duration: 4)) {
vm.x1 += 1
x2 += 1
}
}
}
func text() -> String {
print("x1: \(vm.x1) + x2 = \(x2) = \(vm.x1 + x2)")
return "x1: \(vm.x1) + x2 = \(x2) = \(vm.x1 + x2)"
}
}
class SyncUpdateViewModel: ObservableObject {
var x1: Int = 0
}
struct SyncUpdate: View {
@StateObject var vm: SyncUpdateViewModel = SyncUpdateViewModel()
//@State private var value: Int = 0
var body: some View {
NavigationView {
GeometryReader { proxy in
OtherView(size: proxy.size).environmentObject(vm)
}
}
}
}
Upvotes: 1