Instruction
Displaying Writing Tools via UITextInteraction
Adding Writing Tools to your custom views requires two components. The first necessary component is the UITextInteraction. This class is not Writing Tools specific. It was released in iOS 13 and allows the end user to perform selection gestures on your custom text views.
You also need to implement the UITextInput protocol. This protocol covers a lot of functionality, including text insertion, text selection, caret positioning, and finding the position of text within the view. The UITextInput works with other text related classes like UITextPosition, UITextRange, and others.
Writing Tools is enabled by the addition of a UITextInteraction and it uses the UITextInput protocol to respond to events.
Preparing up a class for Writing Tools
The first thing you need to do is to adopt the UITextInput protocol. Here is a custom text view. Notice that it is a UIView and it also implements the UITextInput protocol.
class CustomSkillsView: UIView, UITextInput{
This class also comes with some TextKit properties that are beyond the scope of this lesson.
private var textStorage = NSTextStorage()
private var layoutManager = NSLayoutManager()
private var textContainer = NSTextContainer()
The key thing is implement some of the UITextInput properties.
var selectedTextRange: UITextRange?
var markedTextRange: UITextRange?
var markedTextStyle: [NSAttributedString.Key: Any]?
var inputDelegate: UITextInputDelegate?
var tokenizer: UITextInputTokenizer = UITextInputStringTokenizer()
Initializers and Configuration
The initializers for this class are fairly straight forward. They all use a setupInteraction() function to initialize the properties. Most importantly, these initializers setups a connection between the layout manager, the text storage. and the text container. This acts very much like a model-view-controller pattern.
The first thing performed by setupInteraction() is add a UITextInteraction to the text view.
private func setupInteraction() {
let textInteraction = UITextInteraction(for: .editable)
textInteraction.textInput = self
addInteraction(textInteraction)
With your interaction put in place, your next step is to prepare all the TextKit components.
textStorage.addLayoutManager(layoutManager)
layoutManager.addTextContainer(textContainer)
textContainer.lineFragmentPadding = 0
You then setup the remaining components of your custom text view.
// Enable user interaction and set appearance
isUserInteractionEnabled = true
backgroundColor = .white
layer.borderColor = UIColor.gray.cgColor
layer.borderWidth = 1
// Initialize selectedTextRange
selectedTextRange = UITextRangeImpl(range: NSRange(location: 0, length: 0))
}
Finally, you need to override canBecomeFirstResponder.
override var canBecomeFirstResponder: Bool {
return true
}
The canBecomeFirstResponder function tells the system that it can be interacted with as the first responder in the responder chain.
Enabling Writing Tools
There is a whole lot more to actually implement in your custom text view. You need to render the text on the screen, enable touch handling, as well as provide SwiftUI support. That’s a lot of code, but what about Writing Tools itself?
You will be happy to know that you already have Writing Tools support. By adding the UITextInteraction by way of addInteraction() in your initializer, you’ve now enabled the basic experience of Writing Tools. Writing Tools will now appear in the callout bar and in the context menu.
You still need to write some UITextInput protocol methods so that Writing Tools can make changes. For instance, imagine Writing Tools provided a new greeting to an email. Here is some code you would implement to replace text in a selection:
func replace(_ range: UITextRange, withText text: String) {
guard let range = range as? UITextRangeImpl else { return }
textStorage.replaceCharacters(in: range.nsRange, with: text)
setNeedsDisplay()
}
This simply takes the range passed into it, and then replaces the characters in the textStorage. The screen refreshes and the user see the updated text. There are several other functions for this protocol which you can see in official documentation, mostly related to text position and text selection.
Once iOS 18.1 gets out of beta, you’ll see the complete working implementation in an upcoming demo.