Highlight
iPadOS 14 引入系统级 Apple Pencil 手写识别功能 Scribble,用户可以直接在
UITextField、UITextView或UISearchBar上手写,并由系统实时转换成文本。
核心内容
以前在 iPad 上用 Apple Pencil 输入文字,经常会遇到一个割裂的流程:先点进文本框,再切换到键盘或单独的手写区域。对表单、搜索框、提醒事项列表这类界面来说,这一步会打断 Pencil 的连续操作。用户手里拿着 Pencil,却要频繁回到触控和键盘逻辑。
Scribble 把手写识别做成 iPadOS 14 的系统输入能力。用户直接在可编辑文本区域写字,系统在设备端识别笔迹,并把结果交给现有文本输入系统。标准 UIKit 文本控件、标准 WebKit 可编辑内容和网页表单默认可用,密码字段除外,因为密码推荐通过 AutoFill 输入。
真正需要开发者介入的地方,是自定义文本体验。自定义文本编辑器要完整实现 UITextInput,因为 Scribble 通过它读取内容、选择范围并修改文本。自定义画布、可点击后变成可编辑的区域、会随 focus 移动的搜索框,则需要用 UIScribbleInteraction 或 UIIndirectScribbleInteraction 告诉系统哪里能写、何时能写、写完后应该聚焦哪个 responder。
这场 session 的核心并不复杂:尽量使用标准文本控件;必须自定义时,把 Pencil 当成一个需要稳定布局和充足书写空间的输入设备来设计。
详细内容
系统默认支持标准文本控件
(06:12)Scribble 依赖已有的 iOS Text Input APIs。UITextField、UITextView、UISearchBar 这类 UIKit 标准控件,以及 WebKit 中的标准可编辑内容和网页表单,会自动接入 Scribble。自定义文本编辑器要重点检查 UITextInput,因为系统需要通过它获取文本内容、选择区间,并把识别后的文字写回视图。
这里没有新的接入代码。开发者的第一步是少写代码:能用标准文本控件就用标准文本控件。只有当视图不是标准文本控件,或者它原本需要一次 tap 才进入编辑状态时,才进入后面的 interaction API。
关键点:
UITextInput是自定义文本编辑器接入 Scribble 的核心协议。UITextInteraction可以让自定义编辑器获得系统标准光标和选择 UI。- 密码字段不支持 Scribble,session 明确建议使用 AutoFill。
用 UIScribbleInteraction 调整书写期间的 UI
(09:15)键盘输入常见的内联补全,在手写时可能挡住笔迹。session 给出的做法是通过已添加到文本字段上的 UIScribbleInteraction 判断当前是否正在处理手写,然后隐藏补全文字。
func updateSearchCompletion() {
customSearchField.hideCompletionText = interaction.isHandlingWriting
}
关键点:
interaction.isHandlingWriting只描述当前 interaction 是否正在处理 Scribble 手写。- 这里没有改变文本输入逻辑,只是避开会盖住笔迹的视觉元素。
- 适用场景是搜索建议、内联补全、占位提示这类会出现在书写区域上的 UI。
(09:35)如果界面一开始就能判断用户可能会用 Pencil,UIScribbleInteraction.isPencilInputExpected 可以用来提前给输入区域留出空间。
override func viewDidAppear(_ animated: Bool) {
if UIScribbleInteraction.isPencilInputExpected {
let lineHeight = textField.font?.lineHeight ?? 17.0
let heightForScribble = lineHeight * 4.0
heightConstraint.constant = heightForScribble
}
}
关键点:
isPencilInputExpected是类属性,用于判断当前环境下 Pencil 输入是否可能发生。- 代码用字体行高算出四行文本高度,让输入区域变得更适合手写。
- session 强调 UI 改动要发生在用户没有写字的时候,避免书写过程中布局移动。
(09:51)另一种做法是在用户写完后再调整布局,入口是 delegate 方法 scribbleInteractionDidFinishWriting。
func scribbleInteractionDidFinishWriting(_ interaction: UIScribbleInteraction) {
let lineHeight = textField.font?.lineHeight ?? 17.0
let heightForScribble = lineHeight * 4.0
heightConstraint.constant = heightForScribble
}
关键点:
scribbleInteractionDidFinishWriting在 Scribble 书写结束后回调。- 它适合把输入框扩展到更舒服的高度,或恢复被手写临时影响的布局。
- 这段代码和前一个例子使用同一套高度计算,差别在触发时机。
在绘图模式中关闭 Scribble
(10:08)有些 app 同时有绘图和文字编辑。用户在画布上写字时,系统无法自动判断这一次是要画线,还是要输入文本。UIScribbleInteractionDelegate 的 shouldBeginAt 可以按当前模式拒绝 Scribble。
func scribbleInteraction(_ interaction: UIScribbleInteraction,
shouldBeginAt location: CGPoint) -> Bool {
return !appIsInDrawingMode()
}
关键点:
location是 Pencil 开始书写的位置,delegate 可以结合当前位置和 app 状态判断。- 返回
false会阻止 Scribble 在这个 view 上开始处理。 - 对绘图、批注、白板类 app,这个判断能避免把用户的笔画误当成文字输入。
用 UIIndirectScribbleInteraction 让非文本区域可写
(10:27)UIIndirectScribbleInteraction 面向另一类问题:某个区域平时不是文本输入控件,但用户自然会在那里写字。Reminders 的空白列表区域、session 示例里的笔记本背面刻字区域,都属于这种场景。
第一步是在承载视图上安装 interaction。
override init(frame: CGRect) {
super.init(frame: frame)
indirectScribbleInteraction = UIIndirectScribbleInteraction(delegate: self)
addInteraction(indirectScribbleInteraction)
...
}
关键点:
UIIndirectScribbleInteraction也添加到 view 上。- delegate 负责把可写区域描述给系统。
- 官方片段中的省略号代表样例视图的其他初始化代码,不属于 Scribble 接入步骤。
(11:48)系统会先询问当前 rect 里有哪些可写元素。示例只有一个刻字区域,所以直接返回一个 identifier。
func indirectScribbleInteraction(_ interaction: UIInteraction,
requestElementsIn rect: CGRect,
completion: @escaping ([ElementIdentifier]) -> Void) {
completion(["EngravingIdentifier"])
}
关键点:
- element identifier 是 app 自己定义的稳定标识。
- 多个可写区域可以返回多个 identifier。
- completion 是异步入口,复杂视图可以先计算命中的元素再返回。
(12:14)接着要告诉系统该元素在 view 中的几何位置。示例允许用户在整个刻字区域写字,所以返回 bounds。
func indirectScribbleInteraction(_ interaction: UIInteraction,
frameForElement elementIdentifier: String) -> CGRect {
return bounds
}
关键点:
frameForElement决定 Scribble 可以接受笔迹的区域。- 你可以返回更小的 rect,让可写区域只覆盖控件的一部分。
- frame 要和用户看到的可写区域一致,否则 Pencil 命中会显得不可靠。
(12:28)当用户真的写下去时,系统需要一个支持文本输入的 responder。示例在需要时创建文本字段,把它设为 first responder,然后通过 completion 返回。
func indirectScribbleInteraction(_ interaction: UIInteraction,
focusElementIfNeeded elementIdentifier: String,
referencePoint focusReferencePoint: CGPoint,
completion: @escaping ((UIResponder & UITextInput)?) -> Void)
{
if editingTextField == nil {
createTextField()
}
editingTextField?.becomeFirstResponder()
completion(editingTextField)
}
关键点:
- 返回值必须同时是
UIResponder和UITextInput,这样系统才能把识别出的文本写进去。 createTextField()是示例 app 的实现细节,作用是在视图层级中安装实际文本字段。becomeFirstResponder()把后续输入路由到这个文本字段。
(12:57)最后,delegate 还要告诉系统这个元素当前是否已经处于聚焦状态。
func indirectScribbleInteraction(_ interaction: UIInteraction,
isElementFocused elementIdentifier: String) -> Bool {
// Indicate if our only element is currently installed and focused
return editingTextField?.isFirstResponder ?? false
}
关键点:
- 这个方法让系统知道是否需要再次执行 focus 流程。
- 示例只有一个元素,所以只检查
editingTextField。 - 多元素场景需要按
elementIdentifier分别判断。
核心启发
1. Pencil 友好的搜索框
做什么:给搜索框加一层 Scribble 适配,手写时隐藏补全提示,必要时扩大输入区域。
为什么值得做:session 的 Spotlight 示例说明,内联补全会干扰笔迹;Messages 示例说明,小输入框会限制长句书写。
怎么开始:给搜索字段添加 UIScribbleInteraction,用 isHandlingWriting 控制补全文字,用 isPencilInputExpected 或 scribbleInteractionDidFinishWriting 调整高度。
2. 绘图 app 的文字标注模式
做什么:在同一张画布里支持画线和文字标注,绘图模式下禁止 Scribble,文字模式下允许手写输入。
为什么值得做:session 明确提到绘图和可编辑文本混合时,应通过 shouldBeginAt 决定 Scribble 是否开始。
怎么开始:在画布视图上添加 UIScribbleInteraction,delegate 里读取当前工具状态,绘图工具激活时返回 false。
3. 空白区域直接新增列表项
做什么:让用户在列表底部空白处手写,自动创建一个新任务、提醒或笔记条目。
为什么值得做:Reminders 的例子展示了这种交互:空白区域原本不是文本控件,但用户自然会在那里写下一条新内容。
怎么开始:在列表容器上添加 UIIndirectScribbleInteraction,把底部空白区域作为 element 返回,在 focusElementIfNeeded 中创建并聚焦新条目的文本输入控件。
4. 产品定制里的刻字输入
做什么:在商品预览图上的指定区域手写刻字内容,例如杯子、笔记本、设备外壳。
为什么值得做:session 的 engraving 示例展示了从非编辑区域到隐藏文本字段的完整链路。
怎么开始:用 requestElementsIn 暴露刻字区域,用 frameForElement 返回可写范围,再把真实文本字段作为 UIResponder & UITextInput 返回给系统。
5. Pencil 输入专项 QA 清单
做什么:为 iPad app 增加一组 Pencil 输入测试:标准文本控件、搜索框、表单、空白列表区、自定义编辑器。
为什么值得做:Scribble 对标准控件默认可用,但 session 提醒开发者检查需要先 tap、布局会移动、空间太窄和自定义 UITextInput 不完整的场景。
怎么开始:先用 Apple Pencil 跑通所有文本入口,再给异常入口分别补 UITextInput、UIScribbleInteraction 或 UIIndirectScribbleInteraction。
关联 Session
- What’s new in PencilKit — PencilKit 在 iPad app 中处理绘图、书写、动画,介绍 PKToolPicker、PKCanvasView、PKStroke 等更新。
- Inspect, modify, and construct PencilKit drawings — 深入 PKDrawing、PKStroke、ink、path、point,适合做 Pencil 输入后的识别和编辑。
- Support hardware keyboards in your app — 讲解 iPadOS 与 Mac Catalyst app 的硬件键盘导航、快捷键、responder chain 与原始键盘事件。
- Build for the iPadOS pointer — 用 pointer interaction API 为 iPad pointer 定制按钮、自定义视图和区域动画。
- Build for iPad — 讲解 iPad app 的多栏布局、列表和低打断导航,是设计 Pencil 输入空间的基础。
评论
GitHub Issues · utterances