I'm encountering unexpected behavior with @Binding in a UIViewRepresentable's Coordinator. The same Coordinator instance returns different values depending on how the property is accessed.
Environment:
- iOS 17+ / Xcode 15+
- SwiftUI with UIViewRepresentable
Issue:
When I access @Binding var test inside the Coordinator:
- ✅ Via
self.testin Coordinator methods: Returns the updated value - ❌ Via
context.coordinator.testin updateUIView: Returns the stale/initial value
Both access the same Coordinator instance (verified by memory address), yet return different values.
Minimal Reproducible Example:
struct ContentView: View {
@State private var test: [Int] = [1, 2, 3, 4, 5]
var body: some View {
VStack {
TestRepresentable(test: $test)
Text("State: \(test.description)")
}
}
}
struct TestRepresentable: UIViewRepresentable {
@Binding var test: [Int]
func makeUIView(context: Context) -> UIButton {
let button = UIButton(type: .system)
button.setTitle("Toggle", for: .normal)
button.addTarget(
context.coordinator,
action: #selector(Coordinator.buttonTapped),
for: .touchUpInside
)
return button
}
func updateUIView(_ uiView: UIButton, context: Context) {
// Log coordinator instance address
let coordAddr = String(describing: Unmanaged.passUnretained(context.coordinator).toOpaque())
print("[updateUIView] Coordinator address: \(coordAddr)")
// Log values
print("[updateUIView] self.test: \(self.test)")
print("[updateUIView] context.coordinator.test: \(context.coordinator.test)")
// These should be the same but they're not!
// self.test shows updated value
// context.coordinator.test shows stale value
}
func makeCoordinator() -> Coordinator {
Coordinator(test: $test)
}
class Coordinator: NSObject {
@Binding var test: [Int]
var idx: Int = 0
init(test: Binding<[Int]>) {
_test = test
}
@objc func buttonTapped() {
idx += 1
if idx < test.count {
test[idx] += 5
}
// Log coordinator instance address
let selfAddr = String(describing: Unmanaged.passUnretained(self).toOpaque())
print("[buttonTapped] Coordinator address: \(selfAddr)")
// Log value - this shows the UPDATED value
print("[buttonTapped] self.test: \(test)")
}
}
}
Actual Output:
[Initial]
[updateUIView] Coordinator address: 0x600001234567
[updateUIView] self.test: [1, 2, 3, 4, 5]
[updateUIView] context.coordinator.test: [1, 2, 3, 4, 5]
[After first tap]
[buttonTapped] Coordinator address: 0x600001234567
[buttonTapped] self.test: [1, 7, 3, 4, 5] ✅ Updated!
[updateUIView] Coordinator address: 0x600001234567 ← Same instance
[updateUIView] self.test: [1, 7, 3, 4, 5] ✅ Updated!
[updateUIView] context.coordinator.test: [1, 2, 3, 4, 5] ❌ Stale!
[After second tap]
[buttonTapped] Coordinator address: 0x600001234567
[buttonTapped] self.test: [1, 7, 8, 4, 5] ✅ Updated!
[updateUIView] Coordinator address: 0x600001234567 ← Same instance
[updateUIView] self.test: [1, 7, 8, 4, 5] ✅ Updated!
[updateUIView] context.coordinator.test: [1, 2, 3, 4, 5] ❌ Still stale!
Questions:
- Why does
context.coordinator.testreturn a stale value when it's the same Coordinator instance? - Is this intended behavior or a bug?
- What's the correct pattern to access Coordinator's @Binding properties in updateUIView?
Workaround Found:
Using self.test instead of context.coordinator.test in updateUIView works, but I'd like to understand why accessing the same property through context yields different results.
Related:
- I've seen suggestions to update
coordinator.parent = selfin updateUIView, but this doesn't explain why the same object's property returns different values. - Binding documentation states it's "a pair of get and set closures," but there's no explanation of how Context affects closure behavior.
Any insights would be greatly appreciated!
This is the link that I found out online with same problem that I have but not answered well.