Highlight
iOS 18 引入的 Writing Tools 让用户可以在任意文本视图中校对、改写和转换文本,开发者只需使用 UITextView/NSTextView(TextKit 2)或 WKWebView 即可获得完整支持(00:14)。
核心内容
用户在 App 中编辑文本时,若想校对语法或改写措辞,通常需要离开应用、借助外部工具完成,再把内容粘贴回来——流程割裂,格式容易丢失。Apple 在 iOS 18、macOS 15 中推出了 Writing Tools,把校对、改写、摘要、列表转换、表格转换等功能集成到原生文本视图内。用户选中文本后,直接在编辑菜单、浮动栏或右键菜单中调用,建议以行内方式呈现,样式、链接、附件等富文本属性在改写后自动保留(03:39)。
对开发者而言,Writing Tools 是开箱即用的:使用 UITextView、NSTextView(需 TextKit 2)或 WKWebView,系统自动暴露入口。处理文本时系统会自动扩展选区到完整句子以获得更好效果,并通过 attributed string 维持原有属性。开发者可以通过新增的代理方法暂停同步、保护特定文本范围(如代码块、引用),确保应用状态与文本操作保持一致(05:28)。
详细内容
默认行为与 TextKit 版本差异
使用 UITextView 或 NSTextView 时,Writing Tools 的体验取决于 TextKit 版本。TextKit 2 提供完整体验——建议以行内方式显示,可直接在文本视图中预览和应用更改;TextKit 1 则退化为面板模式,仅显示处理结果(03:58)。WKWebView 默认行为为 .limited(面板模式),如需完整体验需在 WKWebViewConfiguration 中显式设置为 .complete(08:06)。
监听 Writing Tools 会话
Writing Tools 活跃时会直接修改 text storage,应用应避免在同一时间对文本进行持久化或同步操作。Apple 提供了新的代理方法来监听会话生命周期(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:会话开始时调用,适合暂停自动保存、文本分析等会干扰 text storage 的操作textViewWritingToolsDidEnd:会话结束时调用,恢复之前暂停的状态isWritingToolsActive:只读属性,用于在回调中判断当前是否有 Writing Tools 会话正在进行
控制行为与接受格式
开发者可以通过 writingToolsBehavior 控制体验模式(07:11):
textView.writingToolsBehavior = .limited // 仅面板模式
textView.writingToolsBehavior = .none // 完全禁用
通过 writingToolsAllowedInputOptions 指定文本视图接受的格式类型(07:31):
textView.writingToolsAllowedInputOptions = [.plainText]
textView.writingToolsAllowedInputOptions = [.plainText, .richText, .table]
.plainText:仅接受纯文本.richText:接受富文本(包括样式、链接、附件).table:接受表格转换(需文本视图支持 NSTextTable)
默认假设视图支持纯文本和富文本但不支持表格;若视图能渲染表格,需显式声明。
WKWebView 的配置
WKWebView 的 Writing Tools API 通过 WKWebViewConfiguration 设置,默认行为为 .limited。此外 WKWebView 本身也提供 isWritingToolsActive 属性用于检查会话状态(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:在WKWebViewConfiguration上设置,决定 WKWebView 的 Writing Tools 体验级别isWritingToolsActive:当行为为.limited时此属性始终返回false;仅在完整体验下才能检测到活跃会话
保护特定文本范围
笔记应用中的代码块、邮件应用中的引用内容不应被改写。通过新代理方法返回需要忽略的 NSRange,Writing Tools 不会对这些范围提出修改建议(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:系统传递的处理范围,返回的 NSRange 相对于该范围的子串attributedSubstring:从 textStorage 中取出子串,用于检测其中的代码块或引用- WKWebView 会自动忽略
<blockquote>和<pre>标签内的内容(08:56)
自定义文本视图支持
对于非 UITextView/NSTextView 的自定义文本视图,iOS/iPadOS 上采用 UITextInteraction 即可免费获得 Writing Tools 菜单项。系统通过 UITextInput 协议读写文本和定位弹出窗口,新增的 isEditable 属性用于指示视图是否可编辑(09:58):
protocol UITextInput {
@available(iOS 18.0, macOS 15.0, *)
optional var isEditable: Bool { get }
}
在 macOS 上,自定义视图需实现 NSServicesMenuRequestor 协议并覆盖 validRequestor(forSendType:returnType:),同时为视图添加上下文菜单即可自动获得 Writing Tools 菜单项(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:声明视图支持的剪贴板类型(如纯文本.string、富文本.rtf),返回self表示该视图可以处理writeSelection:将选区内容写入剪贴板供系统读取readSelection:从剪贴板读取处理后的文本并应用到视图(可编辑视图必需实现)
核心启发
- 实现 Writing Tools 会话监听:在
textViewWritingToolsWillBegin中暂停自动保存与同步,在textViewWritingToolsDidEnd中恢复,并在关键回调中检查isWritingToolsActive。- 为什么值得做:Writing Tools 活跃时会直接修改 text storage,若应用同时进行自动保存或云端同步,会导致并发冲突和数据不一致。
- 怎么开始:在 UITextViewDelegate/NSTextViewDelegate 中实现两个新代理方法,将需要暂停的逻辑集中管理。
- 保护不应改写的文本范围:通过
writingToolsIgnoredRangesIn代理方法返回代码块、引用、固定术语等 NSRange,阻止 Writing Tools 对这些区域提出修改建议。- 为什么值得做:用户使用 Writing Tools 校对整段文本时,代码块或引用内容被改写会破坏其原始含义,造成困惑和额外修复工作。
- 怎么开始:在 delegate 中实现
textView(_:writingToolsIgnoredRangesIn:),解析文本属性返回需要忽略的范围;WKWebView 项目无需额外代码,<blockquote>和<pre>标签会被自动忽略。
- 为 WKWebView 显式启用完整体验:在
WKWebViewConfiguration中将writingToolsBehavior设为.complete,使 WebView 中的文本编辑器获得行内预览与直接应用更改的能力。- 为什么值得做:WKWebView 的默认行为是
.limited(仅面板模式),用户只能看到结果后手动复制粘贴,体验明显低于原生文本视图的行内交互。 - 怎么开始:创建
WKWebViewConfiguration时设置writingToolsBehavior = .complete,然后观察isWritingToolsActive确认会话状态。
- 为什么值得做:WKWebView 的默认行为是
- 声明文本视图支持的输入格式:如果自定义文本视图能渲染表格,显式设置
writingToolsAllowedInputOptions包含.table;若仅接受纯文本则只声明.plainText。- 为什么值得做:系统默认假设文本视图不支持表格,如果你的视图具备渲染能力却不声明,用户将无法使用 Writing Tools 的表格转换功能。
- 怎么开始:在文本视图初始化时设置
writingToolsAllowedInputOptions = [.plainText, .richText, .table],根据实际渲染能力选择子集。
关联 Session
- What’s new in UIKit — 了解 UIKit 中文本和输入相关的新变化,包括 Writing Tools 的系统级集成
- What’s new in AppKit — 查看 macOS 上 Writing Tools 与 NSServicesMenuRequestor 的集成细节
- Design App Intents for system experiences — 理解 App Intents 如何驱动系统级体验,与 Writing Tools 形成互补
- Build multilingual-ready apps — 掌握文本输入和多语言场景下的最佳实践,配合 Writing Tools 提升国际化体验
评论
GitHub Issues · utterances