Lumentus
Lumentus

Reputation: 91

Why does a binding in UIViewRepresentables Coordinator have a constant read value

I have been writing a UIViewRepresentable and noticing some curios effects in regards to a binding I'm passing into the view. When I read the bindings value in the coordinator through the saved UIViewRepresentable the value is always the value that it was initialized with. Trying to update the same binding however triggers an update in the surrounding UI.

UI not updating correctly

This is code produces this behavior:

struct NativeTextView: UIViewRepresentable {
    @Binding var text: String
    func makeUIView(context: Context) -> UITextField {
        let view = UITextField()
        view.borderStyle = .roundedRect
        view.addTarget(
            context.coordinator,
            action: #selector(Coordinator.updateText(sender:)),
            for: .editingChanged
        )
        return view
    }
    func updateUIView(_ uiView: UITextField, context: Context) {
        context.coordinator.updateUI(uiView)
    }
    func makeCoordinator() -> Coordinator {
        Coordinator(_text)
    }
    class Coordinator: NSObject {
        @Binding var text: String
        init(_ text: Binding<String>){
            _text = text
        }
        @objc func updateText(sender: UITextField){
            text=sender.text!
        }
        func updateUI(_ uiView: UITextField) {
            uiView.text = text
        }
    }
}

If I hover give my updateUI method a NativeTextView parameter, and use the .text field of it through the parameter, I read the correct value and the UI works correctly:

UI working correctly

struct NativeTextView: UIViewRepresentable {
    @Binding var text: String
    func makeUIView(context: Context) -> UITextField {
        let view = UITextField()
        view.borderStyle = .roundedRect
        view.addTarget(
            context.coordinator,
            action: #selector(Coordinator.updateText(sender:)),
            for: .editingChanged
        )
        return view
    }
    func updateUIView(_ uiView: UITextField, context: Context) {
        context.coordinator.updateUI(uiView, view: self)
    }
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
    class Coordinator: NSObject {
        var myView: NativeTextView
        init(_ view: NativeTextView){
            self.myView=view
        }
        @objc func updateText(sender: UITextField){
            myView.text=sender.text!
        }
        func updateUI(_ uiView: UITextField, view: NativeTextView) {
            uiView.text = view.text
        }
    }
}

It seems that the binding retains the ability to write to the outside @State variable but does not manage to access the current states value correctly. I'm guessing that this has something to do with the recreation of the NativeTextView view when SwiftUI notices an update of the @State, but I have not been able to find any documentation that would explain this behavior. Does anyone know why this happens?

PS: for completeness this is my ContentViews body:

ZStack {
    Color.red
    VStack {
        Text(test)
            .padding()
            .onTapGesture() {
                test = "Bla"
            }
            NativeTextView(text: $test)
        }
}

Upvotes: 4

Views: 1069

Answers (2)

malhal
malhal

Reputation: 30746

It's because you only give the binding to the Coordinator once, here:

    func makeCoordinator() -> Coordinator {
        Coordinator(_text)
    }

The text binding is a value that is changed every time the NativeTextView UIViewRepresentable is re-init by the parent View's body, so you need to give the new value of the binding to the coordinator in update. So you can do:

    func updateUIView(_ uiView: UITextField, context: Context) {
        context.coordinator._text = _text
    }

Upvotes: -1

sergey mishunin
sergey mishunin

Reputation: 11

Coordinator in swiftui is like delegate, so you update your view with bind value in updateUIView like this: uiView.text = text, and you don't need updateUI method in Coordinator (view.text has your initial text in it in that method)

Upvotes: 1

Related Questions