WWDC Quick Look đź’“ By SwiftGGTeam
Get started with Writing Tools

Get started with Writing Tools

Watch original video

Highlight

Writing Tools introduced in iOS 18 lets users proofread, rewrite, and transform text in any text view. Developers only need to use UITextView/NSTextView (TextKit 2) or WKWebView to get full support (00:14).

Core Content

When users edit text in an app and want to proofread grammar or rewrite wording, they typically leave the app, use external tools, and paste content back—a fragmented workflow where formatting is easily lost. Apple introduced Writing Tools in iOS 18 and macOS 15, integrating proofreading, rewriting, summarization, list conversion, and table conversion into native text views. After selecting text, users invoke it from the edit menu, floating bar, or context menu. Suggestions appear inline, and rich text attributes like styles, links, and attachments are automatically preserved after rewriting (03:39).

For developers, Writing Tools works out of the box: use UITextView, NSTextView (TextKit 2 required), or WKWebView and the system exposes the entry points automatically. When processing text, the system automatically expands the selection to complete sentences for better results and maintains original attributes via attributed strings. Developers can use new delegate methods to pause syncing and protect specific text ranges (like code blocks and quotes), keeping app state consistent with text operations (05:28).

Detailed Content

Default Behavior and TextKit Version Differences

When using UITextView or NSTextView, the Writing Tools experience depends on the TextKit version. TextKit 2 provides the full experience—suggestions display inline, and you can preview and apply changes directly in the text view; TextKit 1 falls back to panel mode, showing only processing results (03:58). WKWebView defaults to .limited (panel mode); for the full experience, explicitly set it to .complete in WKWebViewConfiguration (08:06).

Monitoring Writing Tools Sessions

When Writing Tools is active, it directly modifies text storage—apps should avoid persisting or syncing text at the same time. Apple provides new delegate methods to monitor session lifecycle (05:28):

func textViewWritingToolsWillBegin(_ textView: UITextView) {
    // Take necessary steps to prepare. For example, disable iCloud sync.
}

func textViewWritingToolsDidEnd(_ textView: UITextView) {
    // Take necessary steps to recover. For example, reenable iCloud sync.
}

if !textView.isWritingToolsActive {
    // Do work that needs to be avoided when Writing Tools is interacting with text view
    // For example, in the textViewDidChange callback, app may want to avoid certain things
    // when Writing Tools is active
}
  • textViewWritingToolsWillBegin: Called when a session starts—good for pausing auto-save, text analysis, and other operations that interfere with text storage
  • textViewWritingToolsDidEnd: Called when a session ends—restore previously paused state
  • isWritingToolsActive: Read-only property for checking whether a Writing Tools session is in progress in callbacks

Controlling Behavior and Accepted Formats

Developers can control the experience mode via writingToolsBehavior (07:11):

textView.writingToolsBehavior = .limited  // Panel-only mode.
textView.writingToolsBehavior = .none     // Completely disabled.

Specify accepted format types via writingToolsAllowedInputOptions (07:31):

textView.writingToolsAllowedInputOptions = [.plainText]
textView.writingToolsAllowedInputOptions = [.plainText, .richText, .table]
  • .plainText: Accept plain text only
  • .richText: Accept rich text (including styles, links, attachments)
  • .table: Accept table conversion (requires text view support for NSTextTable)

By default, the view is assumed to support plain and rich text but not tables; if your view can render tables, declare it explicitly.

WKWebView Configuration

WKWebView’s Writing Tools API is configured via WKWebViewConfiguration, defaulting to .limited. WKWebView also provides an isWritingToolsActive property to check session status (07:55):

// For `WKWebView`, the `default` behavior is equivalent to `.limited`
extension WKWebViewConfiguration {
    @available(iOS 18.0, *)
    open var writingToolsBehavior: UIWritingToolsBehavior { get set }
}

extension WKWebViewConfiguration {
    @available(macOS 15.0, *)
    open var writingToolsBehavior: NSWritingToolsBehavior { get set }
}

extension WKWebView {
    /// If the Writing Tools behavior on the configuration is `.limited`, this will always be `false`.
    @available(iOS 18.0, macOS 15.0, *)
    open var isWritingToolsActive: Bool { get }
}
  • writingToolsBehavior: Set on WKWebViewConfiguration to determine the Writing Tools experience level for WKWebView
  • isWritingToolsActive: Always returns false when behavior is .limited; only detects active sessions in full experience mode

Protecting Specific Text Ranges

Code blocks in note apps and quoted content in email apps shouldn’t be rewritten. Return NSRanges to ignore via the new delegate method, and Writing Tools won’t suggest modifications for those ranges (08:48):

// Returned `NSRange`s are relative to the substring of the textView's textStorage from `enclosingRange`
func textView(_ textView: UITextView, writingToolsIgnoredRangesIn
        enclosingRange: NSRange) -> [NSRange] {
    let text = textView.textStorage.attributedSubstring(from: enclosingRange)
    return rangesInappropriateForWritingTools(in: text)
}
  • enclosingRange: The processing range passed by the system; returned NSRanges are relative to the substring of this range
  • attributedSubstring: Extracts the substring from textStorage to detect code blocks or quotes within
  • WKWebView automatically ignores content inside <blockquote> and <pre> tags (08:56)

Custom Text View Support

For custom text views other than UITextView/NSTextView, adopting UITextInteraction on iOS/iPadOS gives you Writing Tools menu items for free. The system reads/writes text and positions popovers via the UITextInput protocol; the new isEditable property indicates whether the view is editable (09:58):

protocol UITextInput {
    @available(iOS 18.0, macOS 15.0, *)
    optional var isEditable: Bool { get }
}

On macOS, custom views need to implement the NSServicesMenuRequestor protocol and override validRequestor(forSendType:returnType:), plus add a context menu to the view to automatically get Writing Tools menu items (11:05):

class CustomTextView: NSView, NSServicesMenuRequestor {
    override func validRequestor(forSendType sendType: NSPasteboard.PasteboardType?,
                                 returnType: NSPasteboard.PasteboardType?) -> Any? {
        if sendType == .string || sendType == .rtf {
            return self
        }
        return super.validRequestor(forSendType: sendType, returnType: returnType)
    }

    nonisolated func writeSelection(to pboard: NSPasteboard,
                                    types: [NSPasteboard.PasteboardType]) -> Bool {
        // Write plain text and/or rich text to pasteboard
        return true
    }

    // Implement readSelection(from pboard: NSPasteboard)
    // as well for editable view
}
  • validRequestor: Declares pasteboard types the view supports (e.g., plain text .string, rich text .rtf); returning self means the view can handle them
  • writeSelection: Writes selected content to the pasteboard for the system to read
  • readSelection: Reads processed text from the pasteboard and applies it to the view (required for editable views)

Core Takeaways

  • Implement Writing Tools session monitoring: Pause auto-save and sync in textViewWritingToolsWillBegin, restore in textViewWritingToolsDidEnd, and check isWritingToolsActive in key callbacks.
    • Why it’s worth it: Writing Tools directly modifies text storage while active—concurrent auto-save or cloud sync causes conflicts and data inconsistency.
    • How to start: Implement the two new delegate methods in UITextViewDelegate/NSTextViewDelegate and centralize logic that needs pausing.
  • Protect text ranges that shouldn’t be rewritten: Return NSRanges for code blocks, quotes, fixed terms, etc. via writingToolsIgnoredRangesIn to prevent Writing Tools from suggesting changes.
    • Why it’s worth it: When users proofread an entire paragraph, rewriting code blocks or quotes destroys their original meaning, causing confusion and extra fix-up work.
    • How to start: Implement textView(_:writingToolsIgnoredRangesIn:) in the delegate, parse text attributes, and return ranges to ignore; WKWebView projects need no extra code—<blockquote> and <pre> tags are automatically ignored.
  • Explicitly enable full experience for WKWebView: Set writingToolsBehavior to .complete in WKWebViewConfiguration so text editors in WebView get inline preview and direct apply.
    • Why it’s worth it: WKWebView defaults to .limited (panel mode)—users only see results and must copy-paste manually, a noticeably worse experience than native text views’ inline interaction.
    • How to start: Set writingToolsBehavior = .complete when creating WKWebViewConfiguration, then observe isWritingToolsActive to confirm session status.
  • Declare supported input formats: If your custom text view can render tables, explicitly set writingToolsAllowedInputOptions to include .table; if plain text only, declare .plainText.
    • Why it’s worth it: The system assumes text views don’t support tables by default—if your view can render them but you don’t declare it, users can’t use Writing Tools’ table conversion.
    • How to start: Set writingToolsAllowedInputOptions = [.plainText, .richText, .table] at text view initialization, choosing the subset based on actual rendering capability.

Comments

GitHub Issues · utterances