WWDC Quick Look 💓 By SwiftGGTeam
Read between the strokes with PencilKit

Read between the strokes with PencilKit

观看原视频

Highlight

Apple 将设备端手写识别引擎 PKStrokeRecognizer 开放给第三方开发者,支持 29 种语言,并新增 Bézier 路径互转、笔画切割和稳定 Stroke ID,让 PencilKit 从画板组件升级为可嵌入任意渲染管线的底层工具。

核心内容

手写识别:从系统独占到人人可用

在 iOS 27 之前,只有 Freeform 和 Notes 能用上 Apple 顶级的设备端手写识别。第三方 App 要么接入昂贵的第三方 SDK,要么用 Vision 框架自己拼凑,效果远不及系统原生。

现在 PKStrokeRecognizer 向所有开发者开放。它是个 Swift Actor,所有方法都是异步的,识别过程不会阻塞主线程。模型完全离线,随系统内置,不需要联网,也不消耗额外的存储空间。

01:46

PKStrokeRecognizer 提供三项核心能力:

  • Recognized text:返回最可能的识别结果,适合实时显示用户写了什么
  • Indexable content:返回所有候选词拼接成的字符串,适合 Spotlight 索引
  • Search:在画布内搜索关键词并返回匹配区域的边界框

03:41

打破 PKCanvasView 的围墙

PencilKit 底层用三次均匀 B 样条(B-spline)存储笔画路径。这种表示方式对绘制很友好,但跟业界更常见的 Bézier 路径不兼容。

iOS 27 新增了路径转换 API,支持 PKStrokePath 和 Bézier 路径之间的无损互转。这意味着你可以把自己的渲染管线里的 Bézier 路径转成 PKStrokePath,再喂给 PKStrokeRecognizer 做识别,完全不需要 PKCanvasView

08:38

更开放的绘图模型

iOS 27 给 PKStrokePKStrokePath 增加了 Identifiable 协议支持,返回稳定的 UUID。Undo、Redo、变换之后,同一笔画的 ID 不变。

PKCanvasView 新增了 canvasViewSelectionDidChange 代理方法,选区变化时自动触发。renderGroupID 也从内部实现变成可控属性,可以手动控制哪些笔画被当作”未干墨水”进行混合渲染。

10:32

笔画切割

iOS 27 引入了两类切割 API:

  • Programmatic erasing:用一段 PKStrokePath 作为橡皮擦路径,程序化地切割笔画,效果跟用户在画布上用橡皮擦一样
  • Substroke extraction:用参数化下标从笔画中提取任意片段,保持纹理粒子的一致性

切割操作在复杂绘图(几千个 Stroke)上开销较大,建议在后台线程执行。

11:36

详细内容

基础手写识别

03:53

import PencilKit

let recognizer = PKStrokeRecognizer()
await recognizer.updateDrawing(drawing)
myLabel.text = await recognizer.recognizedText()

关键点:

  • PKStrokeRecognizer 是 Actor,线程安全
  • updateDrawingrecognizedText 都是异步方法
  • 默认使用设备语言识别,也可通过 preferredLanguages 显式配置
  • 支持 29 种语言,可通过 supportedLanguages 查询
  • Simulator 仅支持拉丁字符语言,中日韩等语言需真机测试

生成可索引内容

05:22

import PencilKit

let recognizer = PKStrokeRecognizer()
await recognizer.updateDrawing(drawing)
if let indexedContent = await recognizer.indexableContent {
    index(text: indexedContent)
}

关键点:

  • indexableContent 返回所有候选词拼接的字符串,适合 Spotlight 索引
  • 手写有歧义时(如 “1” 和 “l”),recognizedText 只给一个最佳答案,indexableContent 给所有候选
  • recognizerVersion 属性标识当前识别模型版本,建议跟索引内容一起持久化,加载时比对版本号决定是否重建索引
  • 每次笔画更新都调用识别器会耗电,建议做节流(throttle)

画布内搜索

06:58

import PencilKit

let recognizer = PKStrokeRecognizer()
await recognizer.updateDrawing(drawing)
let results = await recognizer.search("apple")
for result in results {
    highlight(bounds: result.bounds)
}

关键点:

  • search 返回 SearchResult 数组,每个结果包含匹配区域的 bounds
  • 可结合 UIFindInteraction 实现系统级的查找替换体验
  • 搜索能力也为辅助功能提供了基础:VoiceOver 可以朗读手写内容,搜索让屏幕阅读器能定位到具体单词

核心启发

  • 做儿童识字 App:用 PKStrokeRecognizer 实时检查孩子写的是不是正确汉字。结合 search 判断笔画顺序,用 Substroke extraction 回放书写过程。入口 API:PKStrokeRecognizer.recognizedText()PKStroke.subscript(_:)

  • 做支持手写的笔记 App:把用户的手写内容通过 indexableContent 同步到 Core Spotlight,全文搜索覆盖手写笔记。入口 API:PKStrokeRecognizer.indexableContentrecognizerVersion

  • 做自定义画板:用 Metal 或 SwiftUI Canvas 做自己的渲染层,通过 Bézier 互转把笔画数据喂给 PKStrokeRecognizer。入口 API:PKStrokePath 的 Bézier 转换方法。

  • 做智能批注工具:用 Programmatic erasing API 实现”划掉删除”手势,用户划一条线穿过文字,自动识别并删除对应内容。入口 API:以 PKStrokePath 作为橡皮擦路径的切割方法。

  • 做手写公式计算器:识别用户手写的数学公式,转成 LaTeX 或直接用 PKStrokeRecognizer 识别数字和运算符,计算结果。入口 API:PKStrokeRecognizer.preferredLanguagesrecognizedText()

关联 Session

  • Meet PaperKit — PaperKit 构建在 PencilKit 之上,本 Session 的所有新 API 在 PaperKit 中同样可用
  • SwiftUI — 学习如何在 SwiftUI 中集成 PencilKit 画布
  • Swift Testing — 为手写识别逻辑编写测试
  • UIKit modernizationUIFindInteraction 的系统查找体验集成

评论

GitHub Issues · utterances