Using Text Views in a SwiftUI App

SwiftUI currently lacks native support for text views. (Note: Xcode 12 adds text view support to SwiftUI). If you want people to enter large amounts of text in a SwiftUI app, you have to wrap a UIKit or AppKit text view. This article shows you how to use a text view in an iOS app that uses SwiftUI. Most of this material should also apply to Mac apps. Replace anything that has a UI prefix with NS.

UIViewRepresentable

To use UITextView in a SwiftUI app, you must create a struct for the text view and have it conform to the UIViewRepresentable protocol. The UIViewRepresentable protocol is how you wrap UIKit views in SwiftUI apps.

struct TextView: UIViewRepresentable {

}

Don’t forget to import the UIKit framework.

Create the View

To conform to the UIViewRepresentable protocol, you must add two functions to the struct you created. The first function is makeUIView, which creates and configures the view. It takes an argument of type Context and returns the type of view you want to create, which is UITextView for a text view. The Context type is a type alias for the context where updates to the UIKit view take place. The following code shows an example of creating and configuring a text view:

func makeUIView(context: Context) -> UITextView {
    let view = UITextView()
    view.isScrollEnabled = true
    view.isEditable = true
    view.isUserInteractionEnabled = true
    view.contentInset = UIEdgeInsets(top: 5, 
        left: 10, bottom: 5, right: 5)
    return view
}

At a minimum you must create the view and return it. In the code sample I decided to make the text view scrollable and editable. I also added some padding so the text isn’t on the left edge of the screen.

Update the View

The second function you must write is updateUIView, which handles updates to the view. The updateUIView function takes two arguments: a view and a context.

func updateUIView(_ uiView: UITextView, context: Context) {

}

I will be filling the function later in the article.

Try Creating Your Own Text View

At this point you have enough to add a text view to a SwiftUI app. Create an iOS single view app project in Xcode. Add a file for the TextView struct. Fill the struct with the code I’ve shown so far in the article. Open the ContentView.swift file and replace the text label in the body property with a text view.

var body: some View {
    TextView()
}

Build and run the app. You should be able to type in the text view.

Connecting to Your Data Model

At this point you can type in the text view, but you’ll lose what you type when you quit the app. For a text view to be useful, it needs a connection to your app’s data model.

In the struct for the text view, add a property for the data model. For a simple app, you can create a @State property wrapper that holds the text.

@State var text: String

In a real app you’re more likely to have a reference to the data model in another SwiftUI view. Use the @Binding property wrapper to access data from another SwiftUI view.

@Binding var model: MyModel

Replace MyModel with whatever the name of your data model is.

Now that you have a connection to your data model, you can fill in the updateUIView function. Make the text view show the text from the data model.

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

Coordinators

In order for a UIKit view to communicate with data in SwiftUI, the view needs a coordinator. To conform to the UIViewRepresentable protocol, you must add a new function to the TextView struct, makeCoordinator. The makeCoordinator function creates the coordinator. The return type takes the form StructName.Coordinator.

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

The next step is to set the text view’s delegate to the context’s coordinator. Add the following line of code to makeUIView before the return statement:

view.delegate = context.coordinator

The last step is to add a class for the coordinator. Create a class for the coordinator inside the struct for the text view. Make sure the name of the class matches the name you supply to the makeCoordinator function.

class Coordinator: NSObject, UITextViewDelegate {
    var control: TextView

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

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

The Coordinator class inherits from NSObject, which is the base class for all UIKit and AppKit classes. A coordinator for a text view should conform to UITextViewDelegate so it can respond to text view notifications, such as the contents of the text view changing.

The coordinator needs a property for the control it’s going to coordinate. The control property contains a reference to the text view.

The Coordinator class needs an initializer to set its view, which is the control property in the code sample.

The last block of code tells the coordinator to respond when the contents of the text view change. The function sets the model’s contents to the text view’s contents. If you want the text view to handle other notifications, add the functions to the coordinator.

Summary

To use UIKit views in a SwiftUI, you must perform the following tasks:

  • Create a struct for the view that conforms to the UIViewRepresentable protocol.
  • Write the makeUIView function to create and configure the view.
  • Write the updateUIView function so SwiftUI can update the view.
  • Write the makeCoordinator function to create a coordinator for your view to communicate with SwiftUI data.
  • Create a class for the coordinator.

I have a simple plain text editor project on GitHub if you want to see an example of a SwiftUI project that uses a text view.