VisionKit DataScanner:一行代码搞定实时扫码和文字识别
Capture machine-readable codes and text with VisionKit
2022年6月6日
一句话判断
DataScannerViewController 是 Apple 对「我只需要扫个码/识别下文字,为什么要把 AVFoundation + Vision 搞这么复杂」这个问题的终极回答——一个 UIViewController 子类,带预览、带引导、带高亮,present 就能用。
这场 Session 讲了什么
iOS 上做实时数据扫描,之前的方案要么用 AVFoundation 的 metadata output(只支持机器码),要么把 AVFoundation 和 Vision 组合起来(代码量大,坐标转换麻烦)。iOS 16 的 DataScannerViewController 把这些全部封装成一个开箱即用的 ViewController。
你告诉它要识别什么类型的数据(QR 码、条形码、文字,或它们的组合),然后 present 并调用 startScanning()。它自动提供实时相机预览、引导标签、识别项高亮、点击对焦和双指缩放。坐标全部是 view 坐标系,不用再做 image space 到 Vision 坐标到 view 坐标的痛苦转换。
你还可以指定 region of interest(感兴趣区域)来限制扫描范围,用 view 坐标设置。文字识别支持指定 content type(URL、电话号码等七种类型)和语言提示(iOS 16 新增日语和韩语)。机器码识别支持 Vision 支持的所有码制。
值得深挖的点
三种质量级别的取舍。 qualityLevel 有 fast、balanced、accurate 三种。Fast 牺牲分辨率换取速度,适合识别大号文字和近距离码。Accurate 用最高精度,适合微小 QR 码和细小序列号。Balanced 是默认值,Session 推荐大多数场景先用 balanced,效果不好再切换。我觉得这个设计比让你自己调 resolution 要友好得多。
delegate 和 AsyncStream 两种数据消费方式。 如果你需要自定义高亮(比如给每个识别到的条形码画一个彩色边框),用 delegate 的 didAdd/didUpdate/didRemove 三个方法来管理高亮 view 的生命周期。如果你不需要自定义 UI,用 recognizedItems 这个 AsyncStream 更简洁——它持续更新当前识别到的所有项目。两种方式拿到的数据都按自然阅读顺序排列(对文字识别特别有用)。
代码片段
创建并启动 DataScanner:
import VisionKit
// 指定要识别的数据类型
let types: Set<DataScannerViewController.RecognizedDataType> = [
.barcode(symbologies: [.qr, .ean13]),
.text(textContentType: .URL) // 只识别 URL 类型的文字
]
// 创建并配置 scanner
let scanner = DataScannerViewController(
recognizedDataTypes: types,
qualityLevel: .balanced,
recognizesMultipleItems: true, // 同时识别多个项目
isHighFrameRateTrackingEnabled: true, // 高帧率追踪,让高亮更跟手
isPinchToZoomEnabled: true,
isGuidanceEnabled: true, // 顶部显示引导标签
isHighlightingEnabled: true // 系统自带高亮
)
present(scanner, animated: true) {
try? scanner.startScanning()
}
处理用户点击识别到的项目:
// RecognizedItem 是一个 enum,关联 text 或 barcode
func dataScanner(_ dataScanner: DataScannerViewController,
didTapOn item: RecognizedItem) {
switch item {
case .text(let text):
// text.transcript 是识别到的文字
// 文字精度会随扫描时间推移而提高
handleRecognizedText(text.transcript)
case .barcode(let barcode):
// barcode.payloadStringValue 是条码内容
if let payload = barcode.payloadStringValue {
openURL(from: payload)
}
@unknown default:
break
}
}
自定义高亮的完整 delegate 实现:
var highlights: [RecognizedItem.ID: HighlightView] = [:]
// 新项目出现 → 创建高亮
func dataScanner(_ dataScanner: DataScannerViewController,
didAdd addedItems: [RecognizedItem], allItems: [RecognizedItem]) {
for item in addedItems {
let highlight = HighlightView(for: item)
highlights[item.id] = highlight
// 添加到 overlayContainerView,确保在预览之上
dataScanner.overlayContainerView.addSubview(highlight)
}
}
// 项目移动 → 更新高亮位置
func dataScanner(_ dataScanner: DataScannerViewController,
didUpdate updatedItems: [RecognizedItem], allItems: [RecognizedItem]) {
for item in updatedItems {
highlights[item.id]?.animate(to: item.bounds)
}
}
// 项目消失 → 移除高亮
func dataScanner(_ dataScanner: DataScannerViewController,
didRemove removedItems: [RecognizedItem], allItems: [RecognizedItem]) {
for item in removedItems {
highlights.removeValue(forKey: item.id)?.removeFromSuperview()
}
}
最佳实践
- 使用
isSupported检查设备是否支持(2018 及之后有 Neural Engine 的 iPhone/iPad),不支持时隐藏相关 UI 入口。 - 使用
isAvailable检查用户是否授权了摄像头访问,以及设备是否有摄像头限制。 - 如果你能预判语言环境,传
languages参数给文字识别,尤其在两种语言看起来相似时很有帮助。 - 用
overlayContainerView添加自定义高亮,不要添加到 scanner 的其他 view 上。 - 每个项目的
bounds是四个角点(不是矩形),因为透视变换下文字可能是梯形。
还有什么值得关注
capturePhoto()可以在扫描的同时拍摄高质量静态照片。- iOS 16 的 Live Text 新增日语和韩语支持,用
supportedTextRecognitionLanguages获取完整列表。 - DataScanner 返回的文字项目按自然阅读顺序排列,第一个元素就是用户最先读到的。