UITextView in a UIViewRepresentable
I’ve seen a lot of code shared online getting the Coordinator wrong so thought I’d share an example of how I believe the Coordinator should be done. makeView returning context.coordinator.textView is taken from Fruta’s PaymentButton.swift but the ideas of the lazy var and textDidChange closure are my own. This means the correct binding value will be set even if a different binding is passed in since last time the representable struct was init. An improvement I’d like to make is to update the text view using font info from `@Environment` if one is set (it’s nil by default).
import SwiftUI
import UIKit
struct UITextViewTest: View {
@State var text = "Hello, World!"
var body: some View {
VStack {
TextField("", text: $text)
MultilineTextField(text: $text)
}
}
}
struct MultilineTextField: UIViewRepresentable {
@Binding var text: String
func makeUIView(context: Context) -> UITextView {
context.coordinator.textView
}
func updateUIView(_ uiView: UITextView, context: Context) {
// update in case the text has changed
uiView.text = text
// update in case there is a new binding
context.coordinator.textDidChange = { newText in
text = newText
}
}
func makeCoordinator() -> Coordinator {
Coordinator()
}
class Coordinator: NSObject, UITextViewDelegate {
lazy var textView: UITextView = {
let textView = UITextView()
textView.font = .preferredFont(forTextStyle: .body) // the default font is smaller than SwiftUI's default for TextField so we set a bigger one here.
textView.delegate = self
return textView
}()
var textDidChange: ((String) -> Void)?
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
return true
}
func textViewDidChange(_ textView: UITextView) {
textDidChange?(textView.text)
}
}
}