入门 Writing Tools
Get started with Writing Tools
2024年6月10日
一句话判断
Writing Tools 自动出现在所有使用 UITextView/NSTextView 的 App 中,你需要做的事情不是”如何启用”,而是”如何正确处理它对你文本存储的修改”。
这场 Session 讲了什么
Writing Tools 是 Apple Intelligence 的一部分,提供校对、改写、摘要、列表化、表格化等文本处理能力。它不是独立 App,而是系统级的文本增强服务——在任何原生文本视图中自动可用。
Session 的核心信息对开发者来说有两层:第一层是”免费获得”——UITextView、NSTextView、WKWebView 都自动支持,不需要写代码。第二层是”需要适配”——Writing Tools 会直接修改你的 text storage,你需要在会话期间暂停同步、避免并发编辑、处理长文本的分块更新,并且可以控制哪些文本范围应该被保护(如代码块和引用)。
演示展示了从校对一封邀请函到把列表文本转换为表格的完整流程,富文本的粗体和链接在转换后都保留了。
值得深挖的点
Writing Tools 对 Text Storage 的侵入式操作
Writing Tools 不是在你文本旁边放一个建议面板——它直接修改你的 text storage。校对建议自动应用,改写结果直接替换原文,表格化会把文本转成 NSTextTable。这是”行内体验”(inline experience)的设计决策,让用户感觉文本在眼前被实时改写。
这意味着你的 App 必须正确处理几个场景:Writing Tools 可能扩展用户的选择范围到完整句子以获得更好的上下文。改写结果可能分多个 chunk 陆续到达,中间状态带动画效果。用户可以点击”原始”按钮在原文和改写结果之间切换——这些切换操作都是对你的 text storage 的修改。
Session 提供了 textViewWritingToolsWillBegin 和 textViewWritingToolsDidEnd 两个 delegate 方法,让你在会话期间暂停数据同步或任何直接操作 text storage 的逻辑。还提供了 isWritingToolsActive 属性,在做文本操作时判断是否和 Writing Tools 并发。如果你不处理这些,Writing Tools 的修改和你的自动保存逻辑可能互相踩踏。
保护特定文本范围:代码块和引用不该被改写
Writing Tools 很聪明,但你不希望它改写你的代码块或邮件引用。新的 delegate 方法 textView(_:writingToolsIgnoredRangesIn:) 让你返回应该被忽略的 NSRange 数组。Writing Tools 不会对这些范围提出修改建议。
WKWebView 中这个功能是自动的——<blockquote> 和 <pre> 标签的内容会被自动跳过。但 UITextView 和 NSTextView 需要你手动指定范围,因为你才是那个知道”哪些文本是代码”的人。
代码片段
处理 Writing Tools 会话生命周期
class MyTextViewDelegate: NSObject, UITextViewDelegate {
func textViewWritingToolsWillBegin(_ textView: UITextView) {
// 暂停同步和直接操作 text storage 的逻辑
syncManager.pause()
autoSaveTimer.invalidate()
}
func textViewWritingToolsDidEnd(_ textView: UITextView) {
// 恢复同步和自动保存
syncManager.resume()
autoSaveTimer = startAutoSaveTimer()
}
}
场景:在 Writing Tools 工作期间避免你的代码和它争抢 text storage。坑:忘记暂停自动保存会导致中间状态被持久化,用户切换回原文后数据不一致。
保护代码块和引用范围
func textView(
_ textView: UITextView,
writingToolsIgnoredRangesIn ranges: NSRange
) -> [NSRange] {
// 返回不应被 Writing Tools 修改的范围
var ignoredRanges: [NSRange] = []
// 保护代码块
for codeBlock in detectedCodeBlocks {
ignoredRanges.append(codeBlock.range)
}
// 保护引用文本
for quote in detectedQuotes {
ignoredRanges.append(quote.range)
}
return ignoredRanges
}
场景:笔记 App 中的代码块和邮件 App 中的引用内容不应被 AI 改写。坑:返回的范围必须基于 text storage 的字符偏移,不是 NSRange(location:length:) 计算错误会导致保护错位。
控制 Writing Tools 的行为级别
// 配置 Writing Tools 的行为
textView.writingToolsBehavior = .complete // 完整行内体验(默认)
textView.writingToolsBehavior = .limited // 仅面板体验
textView.writingToolsBehavior = .none // 完全禁用
// 指定支持的输入类型
textView.writingToolsAllowedInputOptions = [.plainText, .richText, .tables]
// 如果你的 text view 只支持纯文本:
textView.writingToolsAllowedInputOptions = [.plainText]
场景:某些文本输入场景不适合行内改写(如聊天输入框),降级为面板模式。坑:WKWebView 的默认行为是 .limited,需要显式设置为 .complete 才能获得完整体验。
最佳实践
新项目: 如果使用 UITextView 或 NSTextView,确保使用 TextKit 2。TextKit 1 只能获得面板模式的有限体验。设计阶段就考虑哪些文本区域需要保护(代码、引用、公式),提前规划 range 管理逻辑。
已有项目: 用 iOS 18 编译后 Writing Tools 自动出现在你的 App 中,不管你有没有准备好。优先检查你的 text storage 操作逻辑——如果有自动保存、同步、或定时分析文本的功能,必须在 Writing Tools 活跃时暂停。然后检查是否有不该被改写的文本范围,添加保护逻辑。WKWebView 项目记得手动将 behavior 设为 .complete。
还有什么值得关注
- Writing Tools 对富文本的保留能力很强——粗体、链接、附件在改写后通常都能保留。
- 表格化功能需要 text view 支持 NSTextTable,通过
writingToolsAllowedInputOptions控制是否启用。 - 自定义 text view(非 UITextView/NSTextView)也能通过 UITextInteraction 获得基础的面板体验。