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)
}
}
}