4
votes

I hope I'm wrong, but I have not been able to find a SwiftUI equivalent to an editable UITextView. So, I built one using UIViewRepresentable. Populating both a SwiftUI Text and my own view with the ObservableObject works - but updates made in my view are not propagated to the ObservableObject. I must be missing something important with the Binding concept. Any guidance would be appreciated.

import SwiftUI
import Combine

struct ContentView: View {

    @ObservedObject var myOText: MyOText

    var body: some View {
        ScrollView {
            VStack {
                Text("This is a bound Text View")
                    .padding(.top, 10)
                    .font(.headline)

                Text(myOText.inTheCourse)
                    .lineLimit(3)
                    .padding()

                Text("This is a multi-line UITextView wrapper:")
                    .font(.headline)

                MultilineTextView(myOText: myOText)
                    .frame(height: 100)
                    .padding()

                Spacer()
            }
        }
    }
}

struct MultilineTextView: UIViewRepresentable {

    @ObservedObject var myOText: MyOText

    func makeUIView(context: Context) -> UITextView {
        let view = UITextView()
        view.isScrollEnabled = true
        view.isEditable = true
        view.isUserInteractionEnabled = true
        view.textAlignment = .center
        view.font = UIFont(name: "Times New Roman", size: 20)
        return view
    }

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

class MyOText: ObservableObject {
    @Published var inTheCourse: String = "When in the Course of human events, it becomes necessary for one people to dissolve the political bands which have connected them ..."
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView(myOText: MyOText())
    }
}

enter image description here Xcode Version 11.2 beta 2 (11B44), iOS 13.

1
Do changes to inTheCourse propagate to your UIKit view? I ask because in my case changes in my @ObservedObject don't trigger updateUIViewController (using UIViewControllerRepresentable in my case). - Rivera
I am not using it that way. I save the myOText to Core Data, then of course load from Core Data. However, I did a simplified test with a myOText, a TextField and a Text View all connected to the ObservedObject, and typing in the TextField updated the Text View but did not update the myOText - just as you suggest. Must be someway to instruct the coordinator to do that. Maybe Jeff or someone can comment. - JohnSF

1 Answers

6
votes

Your multiline text view needs a coordinator to observe the text updates from UITextView

struct MultilineTextView: UIViewRepresentable {
    @ObservedObject var myOText: MyOText

    func makeUIView(context: Context) -> UITextView {
        let view = UITextView()
        view.isScrollEnabled = true
        view.isEditable = true
        view.isUserInteractionEnabled = true
        view.textAlignment = .center
        view.font = UIFont(name: "Times New Roman", size: 20)
        view.delegate = context.coordinator
        return view
    }

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

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

    class Coordinator: NSObject, UITextViewDelegate {
        var control: MultilineTextView

        init(_ control: MultilineTextView) {
            self.control = control
        }

        func textViewDidChange(_ textView: UITextView) {
            control.myOText.inTheCourse = textView.text
        }
    }
}