Supporting Writing Tools on your app
Learn how to have access and manage support to Writing Tools within text fields.
Writing Tools is one of the main features included in Apple operational systems through Apple Intelligence, allowing users to easily rephrase text using local specialized machine learning models. Let's focus on how developers can take advantage of it in their apps.
Before diving into how we can customize the usage of Writing Tools within our app, it’s important to understand that they are a system feature that is automatically compatible with UIKit and SwiftUI components. Developers using the native text components of these frameworks don’t need to make any extra effort to support this feature in their apps.
On the other hand, there are limited customization options when it comes to implementing a custom version of Writing Tools. Despite this limitation, there are still a few tweaks we can make to better integrate them into our app functionalities.
Defining the behaviors
One way to customize Writing Tools is by defining how they are presented to the user and the type of assistance they provide, based on the app’s specific needs. You can do that by specifying the writingToolsBehavior
property of a text view.
When this property is not explicitly set, the system automatically determines the most suitable experience based on the state of the text view. For example, if a UITextView
is not editable, Writing Tools by default will appear as a pop-up or a modal depending on the platform. By explicitly setting this property, developers adjust the presentation and behavior to align with their app’s requirements.
The Writing Tools behavior property can be set with a value of type UIWritingToolsBehavior
. At the moment there are three options available:
complete
: In this mode, the text is directly replaced with the elaboration provided by the Writing Tools with a fancy animation. Of course, the user has the possibility to confirm or to revert the change.limited
: Here, the manipulated text is presented within a pop-up or modal dialog, offering options to copy the text. If theUITextView
is editable, users can choose to replace the original text with the one produced by the Writing Tools.none
: with this property, the text view won't provide any support for Writing Tools.
import UIKit
struct CustomTextView: UIViewRepresentable {
@Binding var text: String
let onStartTimer: () -> Void
let onStopTimer: () -> Void
func makeUIView(context: Context) -> UITextView {
let textView = UITextView()
textView.text = text
textView.isEditable = true
textView.font = UIFont.systemFont(ofSize: 17, weight: .regular)
// Define writing tools behavior for the view
textView.writingToolsBehavior = .complete
return textView
}
func updateUIView(_ textView: UITextView, context: Context) {
if textView.text != text {
textView.text = text
}
}
}
Tracking the Writing Tools usage
Once we have decided how the Writing Tools will power the UITextView
we can programmatically track their usage and take action if and when needed. We can easily achieve that by defining a Coordinator
class and adhering to the delegation pattern to have deeper control over the Writing tools elaboration stage.
import UIKit
struct CustomTextView: UIViewRepresentable {
@Binding var text: String
let onStartTimer: () -> Void
let onStopTimer: () -> Void
func makeUIView(context: Context) -> UITextView {
let textView = UITextView()
textView.text = text
textView.isEditable = true
textView.font = UIFont.systemFont(ofSize: 17, weight: .regular)
textView.writingToolsBehavior = .complete
// 1. Set up the coordinator object as the text view delegate
textView.delegate = context.coordinator
return textView
}
func updateUIView(_ textView: UITextView, context: Context) {
if textView.text != text {
textView.text = text
}
}
func makeCoordinator() -> Coordinator {
Coordinator(parent: self)
}
// Declaration of the coordinator of the text view conforming with the UITextViewDelegate protocol
class Coordinator: NSObject, UITextViewDelegate {
var parent: CustomTextView
init(parent: CustomTextView) {
self.parent = parent
}
}
}
In the example above there are two important steps that were taken:
- Defining the coordinator object as the delegate of the text view
Within themakeUIView(context:)
method, we assign this class as the delegate for theUITextView
. - Connecting the custom text view to the coordinator
The declaredCoordinator
class has a property calledparent
which represents theCustomTextView
object.
Writing Tools started
To monitor when Writing Tools is summoned by the user implement the textViewWritingToolsWillBegin(_:)
method, defined in the UITextViewDelegate
protocol, in the Coordinator
class.
import UIKit
struct CustomTextView: UIViewRepresentable {
@Binding var text: String
let onStartTimer: () -> Void
let onStopTimer: () -> Void
func makeUIView(context: Context) -> UITextView { ... }
func updateUIView(_ textView: UITextView, context: Context) { ... }
func makeCoordinator() -> Coordinator { ... }
class Coordinator: NSObject, UITextViewDelegate {
var parent: CustomTextView
init(parent: CustomTextView) {
self.parent = parent
}
func textViewWritingToolsWillBegin(_ textView: UITextView) {
// Start the timer when Writing Tools is summoned
parent.onStartTimer()
}
}
}
In the example above as soon as the textViewWritingToolsWillBegin(_:)
is called the timer starts performing the onStartTimer()
function passed as a parameter to our custom view.
This method could be used when you need to pass text to some asynchronous function connected to network calls or when you synchronize the textual information with cloud services, for example.
Writing Tools finished
To monitor when Writing Tools is closed by the user implement the textViewWritingToolsDidEnd()
method, defined in the UITextViewDelegate
protocol, in the Coordinator
class.
import UIKit
struct CustomTextView: UIViewRepresentable {
@Binding var text: String
let onStartTimer: () -> Void
let onStopTimer: () -> Void
func makeUIView(context: Context) -> UITextView { ... }
func updateUIView(_ textView: UITextView, context: Context) { ... }
func makeCoordinator() -> Coordinator { ... }
class Coordinator: NSObject, UITextViewDelegate {
var parent: CustomTextView
init(parent: CustomTextView) {
self.parent = parent
}
func textViewWritingToolsWillBegin(_ textView: UITextView) { ... }
func textViewWritingToolsDidEnd(_ textView: UITextView) {
// Stop the timer when Writing Tools is dismissed
parent.onStopTimer()
}
func textViewDidChange(_ textView: UITextView) {
if parent.text != textView.text {
parent.text = textView.text
}
}
}
}
In this way, an action can be performed when the Writing Tools finishes to manipulate the text inside the text view. In this example, we call the function that stops the timer, but you could use this method to restore network requests or cloud synchronization actions.
Implementation in SwiftUI
Now that we have defined our custom text view component it's time to display it in a SwiftUI View.
struct ContentView: View {
@State var textContent: String = """
On a quiet autumn evening, the golden leaves danced in the wind, whispering stories of summer's end.
The sun dipped below the horizon, painting the sky in shades of pink and orange. Nature sighed in contentment, preparing for a peaceful slumber.
"""
@State var elapsedTime: TimeInterval = 0
@State var timer: Timer? = nil
var body: some View {
NavigationStack {
Form {
CustomTextView(
text: $textContent,
onStartTimer: startTimer,
onStopTimer: stopTimer
)
.frame(height: 300)
HStack {
Spacer()
Text("Elapsed Time: \(String(format: "%.2f", elapsedTime)) seconds")
.font(.headline)
.foregroundColor(.gray)
Spacer()
}
}
.navigationTitle("Writing Tools ✏️")
}
}
func startTimer() {
stopTimer() // Stop any existing timer before starting a new one
timer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true) { _ in
elapsedTime += 0.01
}
}
func stopTimer() {
timer?.invalidate()
timer = nil
}
}
Developers need to rely on text components from UIKit such as UITextView
and UITextField
to have the maximum control over how Writing Tools operates with the content of their apps. Despite few customization options, developers can take the experience of using writing tools to another level, enhancing the way user interact with text.