UITextView in a UIViewRepresentable

Published by malhal on

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)
        }
    }
}
Categories: SwiftUI