Writing Tools API: Integrating System-Wide Rewrite and Summarize in Your App


Your users are already using Writing Tools across the system — in Notes, Mail, and Safari. When they switch to your app and the same rewrite, proofread, and summarize capabilities just vanish, the experience feels broken. The good news: Apple made adoption borderline trivial for standard text views, and only slightly more involved for custom ones.

This post covers how Writing Tools integrates with SwiftUI and UIKit text surfaces, how to control its behavior, and how to support it in fully custom text editors. We will not cover the Foundation Models framework for running your own on-device inference — that has its own dedicated post.

Contents

The Problem

Imagine you are building a review editor for a Pixar movie database app. Users write detailed reviews, and you want them to have the same AI-assisted writing experience they get in Notes or Mail. Without Writing Tools support, your TextEditor might silently swallow the system’s rewrite suggestions, or worse, your custom text rendering surface has no idea the feature even exists.

Consider this custom text view that manages its own attributed string:

struct MovieReviewEditor: View {
    @State private var reviewText = ""
    @State private var isEditing = false

    var body: some View {
        VStack(alignment: .leading, spacing: 12) {
            Text("Review for: Toy Story 5")
                .font(.headline)

            // A plain TextEditor gets Writing Tools for free...
            TextEditor(text: $reviewText)
                .frame(minHeight: 200)

            // ...but what about a fully custom text rendering surface?
            CustomRichTextCanvas(text: $reviewText)
        }
        .padding()
    }
}

The TextEditor in this example works out of the box. The CustomRichTextCanvas does not. Bridging that gap is what the Writing Tools API is for.

How Writing Tools Works Under the Hood

Writing Tools is part of Apple Intelligence, introduced in iOS 18.2, macOS 15.2, and iPadOS 18.2. When a user selects text in a supported view and taps the Writing Tools option, the system takes the selected text, processes it through an on-device or server-side model (depending on the operation), and delivers transformed text back to the view.

The key architectural detail: Writing Tools is not a framework you import. It is a system service that communicates with text views through existing protocols — primarily UITextInteraction and the UITextInput protocol on UIKit, and through built-in modifiers in SwiftUI.

There are three core operations:

  • Rewrite — Rephrases selected text in a different tone (friendly, professional, concise)
  • Proofread — Corrects grammar, spelling, and punctuation with tracked-change-style suggestions
  • Summarize — Condenses selected text into key points, a list, or a table

Note: Writing Tools requires Apple Intelligence to be enabled on the device. On devices without Apple Intelligence support (older hardware, unsupported regions), the API calls are harmless no-ops — your UI stays intact.

Automatic Support in Standard Views

If your app uses SwiftUI’s TextEditor, TextField, or UIKit’s UITextView, Writing Tools works automatically. There is zero code to write. Apple designed it this way deliberately — the system recognizes standard text input surfaces and injects the Writing Tools UI.

Here is a minimal but complete review editor that supports Writing Tools out of the box:

import SwiftUI

struct PixarReviewView: View {
    @State private var movieTitle = "Inside Out 2"
    @State private var reviewBody = """
        Riley's emotional journey continues with new emotions \
        joining the crew. The animation is breathtaking and \
        the story hits deeper themes about growing up.
        """

    var body: some View {
        NavigationStack {
            Form {
                Section("Movie") {
                    TextField("Title", text: $movieTitle)
                }

                Section("Your Review") {
                    TextEditor(text: $reviewBody)
                        .frame(minHeight: 150)
                }
            }
            .navigationTitle("Write Review")
        }
    }
}

Select the review text, tap Writing Tools in the callout menu, and you get the full suite of rewrite, proofread, and summarize options. No additional imports, no opt-in flags.

Tip: The same automatic behavior applies to UITextView in UIKit. If you are maintaining an older UIKit codebase, your existing text views already support Writing Tools on iOS 18.2+.

Controlling Writing Tools Behavior

Automatic does not mean uncontrollable. SwiftUI provides the writingToolsBehavior(_:) modifier to fine-tune how Writing Tools interacts with your text views. The equivalent in UIKit is the writingToolsBehavior property on any UITextInput conforming view.

The behavior enum has three cases:

// Full support -- inline rewrites and the panel
// (default for editable views)
TextEditor(text: $reviewBody)
    .writingToolsBehavior(.complete)

// Limited support -- only the panel, no inline replacements
TextEditor(text: $reviewBody)
    .writingToolsBehavior(.limited)

// No support -- opt out entirely
TextEditor(text: $reviewBody)
    .writingToolsBehavior(.none)

When .limited makes sense

The .limited behavior disables inline animated rewrites but keeps the Writing Tools panel accessible. This is the right choice when your view needs to validate or transform text before accepting changes — for example, a code editor where arbitrary AI rewrites could break syntax, or a form field with strict formatting rules.

Opting out with .none

Some fields should never be rewritten by AI. A movie’s release year, a rating score, a username field — these are factual or system-controlled values. Use .none for any field where AI-generated text would be incorrect or confusing:

struct MovieMetadataForm: View {
    @State private var title = "Coco"
    @State private var year = "2017"
    @State private var synopsis = ""

    var body: some View {
        Form {
            // Factual field -- no rewriting
            TextField("Release Year", text: $year)
                .writingToolsBehavior(.none)

            // Creative field -- full Writing Tools support
            TextEditor(text: $synopsis)
                .writingToolsBehavior(.complete)
                .frame(minHeight: 120)
        }
    }
}

Apple Docs: WritingToolsBehavior — SwiftUI

Supporting Writing Tools in Custom Text Views

If you have built a custom text view — say, a rich text editor using NSTextStorage and NSLayoutManager, or a completely custom rendering pipeline — you need to explicitly support Writing Tools through UIKit’s delegation APIs.

The key protocol is UITextViewDelegate, which gained Writing Tools-specific callbacks in iOS 18.2. For views conforming to UITextInput directly, you adopt the UIWritingToolsCoordinating protocol.

UITextViewDelegate callbacks

When Writing Tools processes text, it notifies your delegate at three stages:

class ReviewEditorDelegate: NSObject, UITextViewDelegate {

    // Called when Writing Tools is about to start processing
    func textViewWritingToolsWillBegin(
        _ textView: UITextView
    ) {
        // Disable custom undo grouping, live validation, etc.
        disableLiveSpellCheck()
    }

    // Called when Writing Tools finishes
    // (user accepted or cancelled)
    func textViewWritingToolsDidEnd(
        _ textView: UITextView
    ) {
        // Re-enable features you paused
        enableLiveSpellCheck()
        saveReviewDraft(textView.text)
    }
}

These callbacks let you pause expensive or conflicting operations — like custom spell-checking, live word counts, or collaborative editing syncs — while Writing Tools is actively modifying text.

Controlling allowed input with UIWritingToolsAllowedInputOptions

When adopting UIWritingToolsCoordinating, you can specify what kinds of content your view supports through UIWritingToolsAllowedInputOptions. This tells the system which Writing Tools operations make sense for your surface:

class PixarScriptEditor: UITextView {

    override var writingToolsAllowedInputOptions:
        UIWritingToolsAllowedInputOptions {
        // Our script editor handles plain text and rich text,
        // but not tables
        [.plainText, .richText]
    }
}

The available options are:

OptionDescription
.plainTextAllows plain text results
.richTextAllows attributed string results with formatting
.tableAllows table-structured results

By excluding .table, you prevent Writing Tools from generating summarize-as-table output that your custom view cannot render. This avoids a jarring experience where the system produces content your editor silently drops or corrupts.

Apple Docs: UIWritingToolsAllowedInputOptions — UIKit

Advanced Usage

Handling attributed string results

When your text view supports .richText, Writing Tools may return an NSAttributedString with formatting the user chose (bold, italic, lists). Your custom text storage needs to handle this gracefully.

class ScriptTextStorage: NSTextStorage {
    private var backingStore = NSMutableAttributedString()

    // When Writing Tools delivers rich text, merge it
    // while preserving your custom attributes
    func applyWritingToolsResult(
        _ result: NSAttributedString,
        in range: NSRange
    ) {
        beginEditing()

        // Preserve custom attributes (e.g., highlight colors)
        let customAttrs = backingStore.attributes(
            at: range.location,
            effectiveRange: nil
        ).filter { $0.key.rawValue.hasPrefix("com.pixar.") }

        backingStore.replaceCharacters(in: range, with: result)

        // Re-apply custom attributes to the replaced range
        let newRange = NSRange(
            location: range.location,
            length: result.length
        )
        for (key, value) in customAttrs {
            backingStore.addAttribute(
                key, value: value, range: newRange
            )
        }

        endEditing()
    }
}

Warning: Do not assume Writing Tools results preserve your custom NSAttributedString attributes. The system operates on its own copy of the text. Always re-apply custom attributes after accepting a rewrite.

Coordinating with undo management

Writing Tools rewrites should be a single undoable action. If you manage your own UndoManager, group the entire Writing Tools operation:

func textViewWritingToolsWillBegin(_ textView: UITextView) {
    textView.undoManager?.beginUndoGrouping()
}

func textViewWritingToolsDidEnd(_ textView: UITextView) {
    textView.undoManager?.endUndoGrouping()
    // User can now undo the entire rewrite with one Cmd+Z
}

Responding to proofreading suggestions

The proofread operation is distinct from rewrite. It presents tracked-change-style suggestions that the user can accept or reject individually. Your text view receives these as incremental edits through the standard UITextInput replacement methods. If you override replace(_ range:, withText:), make sure it handles rapid sequential calls — proofreading may apply multiple corrections in quick succession.

Performance Considerations

Writing Tools performs the heavy lifting off your process. The on-device model runs in a system daemon, and server-side operations go through Apple’s Private Cloud Compute infrastructure. Your app’s CPU and memory footprint is essentially unaffected during text processing.

That said, there are a few things to watch:

Text size limits. Writing Tools handles passages of reasonable length efficiently, but passing an entire 50,000-word novel through summarize will take noticeably longer. There is no hard-coded limit, but if your app deals with very long documents, consider guiding users to select specific sections rather than selecting all.

Delegate callback frequency. During proofreading, the delegate may receive many rapid text replacement calls. Avoid triggering expensive operations (network syncs, re-layout of the entire document) on every replacement. Debounce or batch your responses:

func textViewWritingToolsDidEnd(_ textView: UITextView) {
    // Batch post-processing instead of reacting to
    // each individual edit
    Task { @MainActor in
        try? await Task.sleep(for: .milliseconds(300))
        recalculateWordCount()
        syncToServer()
    }
}

Animation coordination. When Writing Tools performs an inline rewrite with .complete behavior, the system animates the text transformation. If you have custom animations on your text view (character-by-character typing effects, highlight animations), they may conflict. Pause custom animations in textViewWritingToolsWillBegin(_:) and resume in textViewWritingToolsDidEnd(_:).

Tip: Profile your delegate callbacks with Instruments’ Time Profiler if you notice UI hitches during proofreading on large text blocks. The bottleneck is almost always in your text layout code, not in Writing Tools itself.

When to Use (and When Not To)

ScenarioRecommendation
Standard TextEditor / UITextViewUse the default .complete behavior. Free.
Form fields with strict formattingSet .writingToolsBehavior(.none).
Rich text editor with custom renderingAdopt UIWritingToolsCoordinating.
Code editor or syntax-highlighted viewUse .limited or .none.
Collaborative real-time editorPause conflict resolution during callbacks.
Read-only text displayNot applicable. Editable surfaces only.

The decision is straightforward: if the user types free-form text, enable Writing Tools. If the field has programmatic constraints, limit or disable it. There is no middle ground worth deliberating over.

Note: Writing Tools requires iOS 18.2+, macOS 15.2+, or iPadOS 18.2+, and the device must support Apple Intelligence. Always pair your adoption with a graceful fallback — which, conveniently, is doing nothing. The modifiers and properties are no-ops on unsupported versions.

Summary

  • Standard SwiftUI and UIKit text views support Writing Tools automatically on iOS 18.2+ — zero code required.
  • Use .writingToolsBehavior(.none) on fields where AI rewriting would corrupt data (dates, codes, identifiers).
  • Use .writingToolsBehavior(.limited) when you need the panel but not inline animated replacements.
  • Custom text views adopt UIWritingToolsCoordinating and specify writingToolsAllowedInputOptions to tell the system what content types they handle.
  • Delegate callbacks (writingToolsWillBegin / writingToolsDidEnd) let you pause conflicting operations like spell-check, undo grouping, and network sync.

If you are looking to go beyond system-provided intelligence and run your own on-device models, check out Apple’s Foundation Models Framework for guided generation and custom AI workflows. For on-device language translation with a similar low-effort integration model, see the Translation Framework.