2

What is the best way to give the below UIViewRepresentable isEditing and onCommit properties? I'd like it to have the same exact functionality on call of TextFields in SwiftUI (where you can add code for what to do when the return key is pressed [onCommit] or when the textfield is clicked [isEditing])

struct TextView: UIViewRepresentable {
@Binding var text: String

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

func makeUIView(context: Context) -> UITextView {
    
    let myTextView = UITextView()
    myTextView.delegate = context.coordinator
    
    myTextView.font = UIFont(name: "HelveticaNeue", size: 16)
    myTextView.isScrollEnabled = true
    myTextView.isEditable = true
    myTextView.isUserInteractionEnabled = true
    myTextView.backgroundColor = UIColor(white: 0.0, alpha: 0.00)
    myTextView.textColor = UIColor.black
    
    return myTextView
}

func updateUIView(_ uiView: UITextView, context: Context) {
    uiView.text = text
}

class Coordinator : NSObject, UITextViewDelegate {
    
    var parent: TextView
    
    init(_ uiTextView: TextView) {
        self.parent = uiTextView
        
    }
    
    func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
        if (text == "\n") {
            textView.resignFirstResponder()
        }
        return true
    }
    
    func textViewDidChange(_ textView: UITextView) {
        print("text now: \(String(describing: textView.text!))")
        self.parent.text = textView.text
    }
    
    
    
    
}
}

Side Note: I'm not sure why, but the binding variable that I am passing in is not changing the actual value. I know because I set up a custom binding with get/set properties and the set is not printing the value. (this value should be the text passed into the textview)

let binding = Binding<String>(get: {
        self.bindingVariableName
    }, set: {
        print("set the value")
        self.bindingVariableName = $0
    }
    )
nickcoding
  • 305
  • 8
  • 35

1 Answers1

4

onCommit

You can pass functions as variables to make them execute in the correct place like:

struct TextView: UIViewRepresentable {
    @Binding var text: String

    var onCommit: ()->()
    ...
}

... {
        ...
        func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
            if (text == "\n") {
                textView.resignFirstResponder()
                parent.onCommit() // <- execute here
            }
            return true
        }
}

and use it like:

TextView(text: $text, onCommit: {
    print("Committed")
})

Similar method for detecting changes can be applied


onEditting

You can use a similar method like the onCommit I explained above or if you are using the SwiftUI 2.0, you can observe for changes with the .onChange modifier (if the actual value is changing from the SwiftUI side) like:

struct ContentView: View {
    @State var text: String = ""

    var body: some View {
        TextView(text: $text)
            .onChange(of: text) { value in
                print("text now: " + text)
            }
    }
}

Update the actual @Binding value:

You need to updated the text in the UIKit side by implementing this function in the coordinator:

func textViewDidChange(_ textView: UITextView) {
    parent.text = textView.text
}

Then the actual bound variable gets updated on the text change event of the textField.


Bouns

With this method, you can also forward the configuring step to the SwiftUI side. So the refactored full working code would be like:

struct ContentView: View {
    @State var text: String = ""

    var body: some View {
        TextView(text: $text) { // Configuring the text field
            $0.font = UIFont(name: "HelveticaNeue", size: 16)
            $0.isScrollEnabled = true
            $0.isEditable = true
            $0.isUserInteractionEnabled = true
            $0.backgroundColor = UIColor(white: 0.0, alpha: 0.00)
            $0.textColor = UIColor.black
        }

        onCommit: { // Detecting the commit
            print("Committed with text: " + text)
        }

        .onChange(of: text) { value in // The native way to detect changes of a State
            print("text now: " + text)
        }
    }
}

struct TextView: UIViewRepresentable {
    @Binding var text: String

    var configurate: (UITextView) -> ()
    var onCommit: ()->()

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

    func makeUIView(context: Context) -> UITextView {
        let myTextView = UITextView()
        configurate(myTextView) // Forwarding the configuring step
        myTextView.delegate = context.coordinator
        return myTextView
    }

    func updateUIView(_ uiView: UITextView, context: Context) {
        uiView.text = text
    }

    class Coordinator: NSObject, UITextViewDelegate {
        var parent: TextView

        init(_ uiTextView: TextView) {
            self.parent = uiTextView
        }

        func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
            if (text == "\n") {
                textView.resignFirstResponder()
                parent.onCommit() // Execute the passed `onCommit` method here
            }
            return true
        }

        func textViewDidChange(_ textView: UITextView) {
            parent.text = textView.text // Update the actual variable
        }
    }
}
Mojtaba Hosseini
  • 95,414
  • 31
  • 268
  • 278