WWDC Quick Look 💓 By SwiftGGTeam
Meet Scribble for iPad

Meet Scribble for iPad

观看原视频

Highlight

iPadOS 14 引入系统级 Apple Pencil 手写识别功能 Scribble,用户可以直接在 UITextFieldUITextViewUISearchBar 上手写,并由系统实时转换成文本。


核心内容

以前在 iPad 上用 Apple Pencil 输入文字,经常会遇到一个割裂的流程:先点进文本框,再切换到键盘或单独的手写区域。对表单、搜索框、提醒事项列表这类界面来说,这一步会打断 Pencil 的连续操作。用户手里拿着 Pencil,却要频繁回到触控和键盘逻辑。

Scribble 把手写识别做成 iPadOS 14 的系统输入能力。用户直接在可编辑文本区域写字,系统在设备端识别笔迹,并把结果交给现有文本输入系统。标准 UIKit 文本控件、标准 WebKit 可编辑内容和网页表单默认可用,密码字段除外,因为密码推荐通过 AutoFill 输入。

真正需要开发者介入的地方,是自定义文本体验。自定义文本编辑器要完整实现 UITextInput,因为 Scribble 通过它读取内容、选择范围并修改文本。自定义画布、可点击后变成可编辑的区域、会随 focus 移动的搜索框,则需要用 UIScribbleInteractionUIIndirectScribbleInteraction 告诉系统哪里能写、何时能写、写完后应该聚焦哪个 responder。

这场 session 的核心并不复杂:尽量使用标准文本控件;必须自定义时,把 Pencil 当成一个需要稳定布局和充足书写空间的输入设备来设计。

详细内容

系统默认支持标准文本控件

06:12)Scribble 依赖已有的 iOS Text Input APIs。UITextFieldUITextViewUISearchBar 这类 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 同时有绘图和文字编辑。用户在画布上写字时,系统无法自动判断这一次是要画线,还是要输入文本。UIScribbleInteractionDelegateshouldBeginAt 可以按当前模式拒绝 Scribble。

func scribbleInteraction(_ interaction: UIScribbleInteraction,
                         shouldBeginAt location: CGPoint) -> Bool {
    return !appIsInDrawingMode()
}

关键点:

  • location 是 Pencil 开始书写的位置,delegate 可以结合当前位置和 app 状态判断。
  • 返回 false 会阻止 Scribble 在这个 view 上开始处理。
  • 对绘图、批注、白板类 app,这个判断能避免把用户的笔画误当成文字输入。

UIIndirectScribbleInteraction 让非文本区域可写

10:27UIIndirectScribbleInteraction 面向另一类问题:某个区域平时不是文本输入控件,但用户自然会在那里写字。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)
}

关键点:

  • 返回值必须同时是 UIResponderUITextInput,这样系统才能把识别出的文本写进去。
  • 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 控制补全文字,用 isPencilInputExpectedscribbleInteractionDidFinishWriting 调整高度。

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 跑通所有文本入口,再给异常入口分别补 UITextInputUIScribbleInteractionUIIndirectScribbleInteraction

关联 Session

评论

GitHub Issues · utterances