View in English

  • Global Nav Open Menu Global Nav Close Menu
  • Apple Developer
Search
Cancel
  • Apple Developer
  • News
  • Discover
  • Design
  • Develop
  • Distribute
  • Support
  • Account
Only search within “”

Quick Links

5 Quick Links

Videos

Open Menu Close Menu
  • Collections
  • Topics
  • All Videos
  • About

More Videos

Streaming is available in most browsers,
and in the Developer app.

  • About
  • Summary
  • Transcript
  • Code
  • Dive deeper into Writing Tools

    With Writing Tools, people can proofread, rewrite, and transform text directly within your app. Learn advanced techniques to customize Writing Tools for your app. Explore formatting options and how they work with rich text editing. If you have a custom text engine, learn how to seamlessly integrate the complete Writing Tools experience, allowing edits directly within the text view.

    Chapters

    • 0:00 - Introduction
    • 0:46 - What’s new
    • 2:21 - Customize native text views
    • 4:00 - Rich text formatting
    • 7:41 - Custom text engines
    • 16:58 - Next steps

    Resources

    • Enhancing your custom text engine with Writing Tools
    • Writing Tools
      • HD Video
      • SD Video

    Related Videos

    WWDC24

    • Get started with Writing Tools
  • Search this video…

    Hi and welcome to “Dive deeper into Writing Tools.” I’m Dongyuan. I work on text input and internationalization. Last year, we introduced Writing Tools and showed how to integrate it in your app. Today, we’ll explore some more advanced topics. I’ll show you what’s new in Writing Tools, ways to customize the Writing Tools experience in native text views, how to have Writing Tools work seamlessly with rich text, and finally, how to integrate a full Writing Tools experience if your app has a custom text engine. Let’s get started! With Writing Tools, people can refine their words by rewriting, proofreading, and summarizing text right inside their text views. This year, we’ve added many new features to Writing Tools. With ChatGPT integration, you can also generate content for anything you want to write about, or create images with a simple prompt.

    Writing Tools are now available on visionOS. They work nearly everywhere, including Mail, Notes, and in apps that you create.

    New in iOS, iPadOS, and macOS 26, after describing changes to rewrite text, you can enter a follow-up request.

    For example, you can ask to make the text warmer, more conversational, or more encouraging.

    In addition, Writing Tools are available as Shortcuts so you can take your workflows to the next level with Apple Intelligence. Tools like proofread, rewrite, and summarize can now be used in an automated fashion.

    We’ve also added a variety of APIs to help your app support Writing Tools. You can now get the stock toolbar button and standard menu items, ask Writing Tools to return presentation intents for rich text, or integrate the powerful Writing Tools coordinator into your custom text engine. We’ll dive deeper into the new result options and the coordinator API later.

    Next up, let’s talk about how to customize Writing Tools in native text views provided by the system.

    Last year’s Writing Tools video covers the basics. For native text views, you can get Writing Tools support for free. You can take advantage of lifecycle methods to react to Writing Tools operations like pausing syncing, customize your text view to use limited or full Writing Tools behavior, you can specify ranges you don’t want Writing Tools to rewrite, or use result options to manage support for rich text, lists, or tables. Keep in mind that in last year’s session, result options were named “Allowed Input Options.” They have been renamed to “Writing Tools Result Options” for clarity.

    Although Writing Tools is available when you select text, if your app is text-heavy, consider adding a toolbar button, like we did for Notes and Mail.

    To do that, use UIBarButtonItem in UIKit, or NSToolbarItem in AppKit.

    In the context menu, Writing Tools items are inserted automatically. If you have a custom menu implementation, or if you want to move the Writing Tools items around, you can set automaticallyInsertsWritingToolsItems to false, and use the writingToolsItems API to get the standard items. This year we updated Proofread, Rewrite, and Summary options in the menu. Using the API ensures that you get all updates for free.

    Now, let’s talk about formatting.

    Apps have many kinds of text views. Some text views don’t support rich text, like the search field in Finder. Some text views support Rich Text, like TextEdit.

    Some text views support semantic styles, like Notes. Here you can specify paragraphs with different semantic styles, such as heading, subheading, or block quote. For example, in Notes the result from Writing Tools can utilize native Notes headings, tables, and lists. To tell Writing Tools what kind of text your text view supports, use Writing Tools result options. For plain text views, use the plainText result option. Writing Tools will still use NSAttributedString to communicate with your text view, but you can safely ignore all the attributes inside. For rich text views, like the one in TextEdit, use the richText result option, with or without the list and table options, depending on if your text view supports lists and tables. Writing Tools may add display attributes like bold or italic to the attributed string.

    Apps that have specialized understanding of semantic formats like Notes can use the richText option with the new presentationIntent option. Writing Tools will use NSAttributedString with presentation intents to communicate with your text view.

    You may wonder what's the difference between display attributes and presentation intents.

    In the TextEdit example, Writing Tools produce rich text by using display attributes like bold or italic, and sends the attributed string to the text view. These attributes only carry stylistic information like concrete font sizes, but not semantic style information. In contrast, Notes tries to fully utilize native semantic styles like headers. Although Writing Tools generated the same text underneath, we instead add presentation intents to the attributed string. Notes can then convert presentation intents to its internal semantic styles. From this example, you can see that the headers part is just a header intent without concrete font attributes.

    Note that even if .presentationIntent is specified in the result options, Writing Tools may still add display attributes to the attributed string, because some styles can’t be represented by presentation intents. In this example, Writing Tools use both an emphasized presentation intent, and a strikethrough display attribute to represent the text “crucial and deleted” in the screenshot.

    To summarize, in presentation intent mode we’ll provide styles in the presentation intent form whenever possible. This includes elements like lists, tables, and code blocks.

    Display attributes may still be used for attributes like underlines, subscript, and superscript. And lastly, presentation intents don’t have a default style. Your app is responsible for converting presentation intents into display attributes, or its own internal styles.

    To have Writing Tools understand the semantics of your text better, once you’ve adopted the presentation intent option, you can override the requestContexts method for your text view, and supply a context with presentation intents whenever possible.

    Finally, even if you have a completely custom text engine, we’ve got you covered! You can get the basic Writing Tools experience for free, as long as your text engine has adopted common text editing protocols. On iOS that’s UITextInteraction, or UITextSelectionDisplayInteraction with UIEditMenuInteraction. On macOS, your view needs to adopt NSServicesMenuRequestor this gives your text view Writing Tools, as well as support for features in the Services menu. For details on basic adoption, check out the WWDC24 session.

    If you want to go one step further, you can implement the full Writing Tools experience. This gives Writing Tools the ability to rewrite text in place, provide animation, and show proofreading changes right in line. This year we’ve added Writing Tools coordinator APIs for custom text engines.

    The Writing Tools coordinator manages interactions between your view and Writing Tools.

    You attach a coordinator to your view, and create a delegate to implement the WritingToolsCoordinatorDelegate methods. The delegate prepares the context for Writing Tools to work on, incorporates changes, provides preview objects to use during animations, provides coordinates for Writing Tools to draw proofreading marks, and responds to state changes. To bring it all to life, let me walk you through a quick demo.

    Here I have a custom text engine built using TextKit 2. As you can see, we’ve already implemented common text editing protocols like NSTextInputClient and NSServicesMenuRequestor.

    If I build and run the app, I get the basic Writing Tools support for free. All the results will be shown in a panel. I can do a Rewrite, for example.

    And Replace, or Copy the result.

    Now let’s implement full Writing Tools support.

    In the DocumentView, I’m going add some instance properties that are needed during a Writing Tools session, initialize an NSWritingToolsCoordinator object, set the delegate to self, and assign it to an NSView. I also need to call this `configureWritingTools` in `init`.

    Of course, it complains that DocumentView does not conform to `NSWritingToolsCoordinator.Delegate`.

    Let’s drag in a file that implements all the delegate methods, in a DocumentView extension. You can see that it prepares the context, does text replacement and selection, returns bounding boxes for proofreading, generates previews for animations, etc.

    Let’s build and run the app.

    If I rewrite the text, you can see the animation, and the text change happens directly in the text view.

    And if I proofread, you can see the underlines added by Writing Tools.

    I can also click on the individual suggestions to show what’s changed.

    Now let’s talk about each of those steps in more detail.

    The first thing to do is to create a coordinator, and attach it to a view. Writing Tools coordinator is a UIInteraction in UIKit, and you attach it to your UIView just like other UIInteractions. In AppKit, it’s an instance property on NSView. Once you have a coordinator, you can set your preferred Writing Tools behavior and result options.

    Now, let’s get to the delegate methods. First and foremost, Writing Tools need a context for the current text. A context consists of a piece of text as an NSAttributedString, and a selection range. At minimum, you should include the current text selection in the attributed string. Optionally, you can also include the paragraph before and after the selection— this gives Writing Tools a better understanding of the context around the text. Set the range to what’s currently selected, based on context.attributedString. If nothing is selected, return the whole document as the context and set the range to the cursor location. This gives Writing Tools an opportunity to work on the whole document, when people haven’t specifically selected anything. This is how you provide context. Here I’m showing AppKit, but unless I specifically point it out, you can assume that UIWritingToolsCoordinator and NSWritingToolsCoordinator behave the same way. The delegate methods are async, because large text views may take time to process the underlying text storage. In the body of the function, prepare the text and range depending on the “scope” parameter. Most of the time, you only need to return one context. For sophisticated text views in which text in multiple text storages can be selected at the same time, the coordinator also supports multiple contexts.

    After evaluating your view’s text, Writing Tools delivers suggested changes to your delegate object. In this replace text delegate method, incorporate the change into your view’s text storage. Writing Tools call this method for each distinct change, and may call the method multiple times with different range values for the same context object. Once finished processing, Writing Tools may ask the delegate to update the selected text range.

    To animate the text while Writing Tools are processing, the coordinator asks for preview images for certain ranges of text.

    Text view should return previews by rendering the text on a clear background. During animations, Writing Tools apply the visual effects to the provided preview images instead of to the text itself. On macOS, this is done via two delegate methods. The first delegate method takes an array. You should return at least one preview for the whole range. Optionally, return one preview per line for smoother animation.

    On iOS, UIKit uses UITargetedPreview instead of NSTextPreview, and only one delegate method is used.

    Before and after the actual animation, Writing Tools calls the prepareFor and finish methods. To prepare for the text animation, hide the specific range of text from the text view. Once animation finishes, show the specific range of text again.

    For proofreading, Writing Tools shows an underline for text ranges that were changed. Writing Tools also responds to click events in ranges of text to show the inline proofreading popup.

    To show proofreading marks, the coordinator asks the delegate to return the underline bezier paths for individual ranges. Writing Tools also need the bounding bezier path to respond to click or tap events.

    Lastly, you can implement the optional writingToolsCoordinator:willChangeToState:completion: method to respond to state changes. You may want to perform undo coalescing, stop syncing, or prevent editing, depending on your text view implementation. Conversely, you should inform the coordinator about external changes via the updateRange:withText to make sure Writing Tools operations are in sync with the latest text. Use updateForReflowedText to notify Writing Tools about layout changes in your view. When you call this method, Writing Tools requests new previews, proofreading marks, and other layout-dependent information.

    We’ve seen how to integrate the full Writing Tools experience with your powerful custom text engine. We’ve also released the project I showed earlier as sample code. Check out the sample code and the complete documentation about Writing Tools coordinator to learn more.

    That wraps up the session. What’s next? Try out the new Writing Tools features. Like follow-up adjustments after you describe your changes. Or Writing Tools in Vision Pro, and the Shortcuts app.

    Add a toolbar button, if your app is text-heavy.

    Play with the formatting options to allow Writing Tools to read and write semantic styles like headings, subheadings, and code blocks.

    If you already have a powerful text engine, empower it further with the full Writing Tools experience.

    Also check out Get started with Writing Tools from last year, and the Writing Tools sample code linked below if you want to see how Writing Tools coordinator works in action. Thanks for watching!

    • 11:46 - Attach a coordinator to the view (UIKit)

      // Attach a coordinator to the view
      // UIKit
      
      func configureWritingTools() {
          guard UIWritingToolsCoordinator.isWritingToolsAvailable else { return }
      
          let coordinator = UIWritingToolsCoordinator(delegate: self)
          addInteraction(coordinator)
      }
    • 12:02 - Attach a coordinator to the view (AppKit)

      // Attach a coordinator to the view
      // AppKit
      
      func configureWritingTools() {
          guard NSWritingToolsCoordinator.isWritingToolsAvailable else { return }
             
          let coordinator = NSWritingToolsCoordinator(delegate: self)
      
          coordinator.preferredBehavior = .complete
          coordinator.preferredResultOptions = [.richText, .list]
          writingToolsCoordinator = coordinator
      }
    • 13:06 - Prepare the context

      // Prepare the context
      
      func writingToolsCoordinator(_ writingToolsCoordinator: NSWritingToolsCoordinator,
              requestsContextsFor scope: NSWritingToolsCoordinator.ContextScope,
              completion: @escaping ([NSWritingToolsCoordinator.Context]) -> Void) {
      
          var contexts = [NSWritingToolsCoordinator.Context]()
                      
          switch scope {
          case .userSelection:
              let context = getContextObjectForSelection()
              contexts.append(context)
              break
              // other cases…
          }
              
          // Save references to the contexts for later delegate calls.
          storeContexts(contexts)
          completion(contexts)
      }
    • 13:48 - Respond to text changes from Writing Tools and update selected range

      // Respond to text changes from Writing Tools
      
      func writingToolsCoordinator(_ writingToolsCoordinator: NSWritingToolsCoordinator,
              replace range: NSRange,
              in context: NSWritingToolsCoordinator.Context,
              proposedText replacementText: NSAttributedString,
              reason: NSWritingToolsCoordinator.TextReplacementReason,
              animationParameters: NSWritingToolsCoordinator.AnimationParameters?,
              completion: @escaping (NSAttributedString?) -> Void) {
      }
      
      // Update selected range
      
      func writingToolsCoordinator(_ writingToolsCoordinator: NSWritingToolsCoordinator,
              select ranges: [NSValue],
              in context: NSWritingToolsCoordinator.Context,
              completion: @escaping () -> Void) {
      }
    • 14:41 - Generate preview for animation (AppKit)

      // Generate preview for animation (macOS)
      
      func writingToolsCoordinator(_ writingToolsCoordinator: NSWritingToolsCoordinator,
              requestsPreviewFor textAnimation: NSWritingToolsCoordinator.TextAnimation,
              of range: NSRange,
              in context: NSWritingToolsCoordinator.Context,
              completion: @escaping ([NSTextPreview]?) -> Void) {
      }
          
      func writingToolsCoordinator(_ writingToolsCoordinator: NSWritingToolsCoordinator,
              requestsPreviewFor rect: NSRect,
              in context: NSWritingToolsCoordinator.Context,
              completion: @escaping (NSTextPreview?) -> Void) {
      }
    • 14:58 - Generate preview for animation (UIKit)

      // Generate preview for animation (iOS)
      
      func writingToolsCoordinator(_ writingToolsCoordinator: UIWritingToolsCoordinator,
              requestsPreviewFor textAnimation: UIWritingToolsCoordinator.TextAnimation,
              of range: NSRange,
              in context: UIWritingToolsCoordinator.Context,
              completion: @escaping (UITargetedPreview?) -> Void) {
      }
    • 15:08 - Delegate callbacks before and after animation

      // Generate preview for animation
      
      func writingToolsCoordinator(
          _ writingToolsCoordinator: NSWritingToolsCoordinator,
          prepareFor textAnimation: NSWritingToolsCoordinator.TextAnimation,
          for range: NSRange,
          in context: NSWritingToolsCoordinator.Context,
          completion: @escaping () -> Void) {
      
          // Hide the specific range of text from the text view
      }
      
      func writingToolsCoordinator(
          _ writingToolsCoordinator: NSWritingToolsCoordinator,
          finish textAnimation: NSWritingToolsCoordinator.TextAnimation,
          for range: NSRange,
          in context: NSWritingToolsCoordinator.Context,
          completion: @escaping () -> Void) {
      
          // Show the specific range of text again
      }
    • 15:39 - Delegate callbacks to show proofreading marks

      // Create proofreading marks
      
      func writingToolsCoordinator(_ writingToolsCoordinator: NSWritingToolsCoordinator,
              requestsUnderlinePathsFor range: NSRange,
              in context: NSWritingToolsCoordinator.Context,
              completion: @escaping ([NSBezierPath]) -> Void) {
      }
      
      func writingToolsCoordinator(_ writingToolsCoordinator: NSWritingToolsCoordinator,
              requestsBoundingBezierPathsFor range: NSRange,
              in context: NSWritingToolsCoordinator.Context,
              completion: @escaping ([NSBezierPath]) -> Void) {
      }
    • 0:00 - Introduction
    • In this video we'll review what's new in Writing Tools, how to customize your app's experience, support rich text, and integrate them into custom text engines.

    • 0:46 - What’s new
    • Writings Tools now support integration with ChatGPT, visionOS, follow-up requests for making tone adjustments, and automation using Shortcuts. Writings Tools also provide new APIs to help you to integrate them into your app.

    • 2:21 - Customize native text views
    • If your app uses native text views, you get Writing Tools support for free. You can further customize the experience by adopting lifecycle methods to react to operations like pausing syncing, specifying ignored ranges within text, providing a toolbar button, or customizing context menus.

    • 4:00 - Rich text formatting
    • Writing Tools now support rich text with semantic styles. If your app supports presentation intents such as headings, subheadings, quotes, tables, and lists, you can communicate that information to Writings Tools. Writings Tools will provide results using styles in your app's supported presentation intent whenever possible.

    • 7:41 - Custom text engines
    • If your app uses a custom text engine, you can now enable a fully integrated experience with Writing Tools. The basic Writing Tools experience works automatically, provided you adopt common text editing protocols. The full Writing Tools experience allows Writing Tools to rewrite text directly in place, provide animations, and show proofreading changes inline. For the full experience, use the new Writing Tools coordinator API to integrate them into your custom text engine.

    • 16:58 - Next steps
    • Explore new Writing Tools features and take advantage of customization and rich text support in your app. If you have a custom text engine, enable a full Writing Tools experience by adopting the coordinator API.

Developer Footer

  • Videos
  • WWDC25
  • Dive deeper into Writing Tools
  • Open Menu Close Menu
    • iOS
    • iPadOS
    • macOS
    • tvOS
    • visionOS
    • watchOS
    Open Menu Close Menu
    • Swift
    • SwiftUI
    • Swift Playground
    • TestFlight
    • Xcode
    • Xcode Cloud
    • SF Symbols
    Open Menu Close Menu
    • Accessibility
    • Accessories
    • App Extensions
    • App Store
    • Audio & Video
    • Augmented Reality
    • Design
    • Distribution
    • Education
    • Fonts
    • Games
    • Health & Fitness
    • In-App Purchase
    • Localization
    • Maps & Location
    • Machine Learning
    • Open Source
    • Security
    • Safari & Web
    Open Menu Close Menu
    • Documentation
    • Tutorials
    • Downloads
    • Forums
    • Videos
    Open Menu Close Menu
    • Support Articles
    • Contact Us
    • Bug Reporting
    • System Status
    Open Menu Close Menu
    • Apple Developer
    • App Store Connect
    • Certificates, IDs, & Profiles
    • Feedback Assistant
    Open Menu Close Menu
    • Apple Developer Program
    • Apple Developer Enterprise Program
    • App Store Small Business Program
    • MFi Program
    • News Partner Program
    • Video Partner Program
    • Security Bounty Program
    • Security Research Device Program
    Open Menu Close Menu
    • Meet with Apple
    • Apple Developer Centers
    • App Store Awards
    • Apple Design Awards
    • Apple Developer Academies
    • WWDC
    Get the Apple Developer app.
    Copyright © 2025 Apple Inc. All rights reserved.
    Terms of Use Privacy Policy Agreements and Guidelines