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 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.
Responding to Text Changes in Mac Apps
Responding to text changes has more differences in Mac apps. Let’s look at the code to respond to text changes on Mac.
func textDidChange(_ notification: Notification) {
guard let textView = notification.object
as? NSTextView else {
return
}
control.model.text = textView.string
}
The name of the function is textDidChange
. Instead of taking the text view as an argument, it takes a notification. The object
property of the notification is the object that sent the notification. When entering text, the text view is the object that sends the notification that the text changed. The guard
statement ensures the object that sent the notification is a text view. The property to get the text in NSTextView
is string
, not text
.
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.