WWDC Quick Look 💓 By SwiftGGTeam
Use the camera for keyboard input in your app

Use the camera for keyboard input in your app

观看原视频

Highlight

iOS 15 让 UIKit 控件可以通过相机把现实文字直接输入到文本字段,并用 textContentTypekeyboardTypeUIKeyInput 控制识别内容、入口位置和接收对象。

核心内容

旅行 App 经常让用户填写纸面信息。酒店电话在确认单上,地址在房间手册里,活动时间在传单上。用户以前只能把纸拿近一点,逐个字符输入到表单里。电话和地址很长,输错一位就要回头检查。

iOS 15 把 Live Text(实况文本)接入了键盘输入。用户在文本字段里打开 Text from Camera,系统启动相机,识别画面里的文字,用户选择后插入到当前输入位置。演示里的旅行日记 App 用它填写酒店电话和地址。

第一次演示还有一个问题。相机看到一整块文档文字,用户仍要拖动选择电话号码。识别已经成功,App 还没有告诉系统自己想要什么内容。

Apple 给出的解法很直接:沿用已有的文本输入属性。textContentTypekeyboardType 原本用于 AutoFill(自动填充)和键盘布局。iOS 15 的相机输入也读取这些属性。字段声明为电话,系统就过滤电话;字段声明为完整地址,系统就过滤地址。

第二个问题是入口太隐蔽。编辑菜单要二次点击才出现,候选栏有时显示预测文本,用户不一定知道这里能用相机。iOS 15 新增 UIAction.captureTextFromCamera,开发者可以把相机输入放进工具栏、菜单或自己的按钮里。

最后,接收对象不限定为 UITextFieldUITextView。只要 responder(响应者)实现 UIKeyInput,相机识别出的文字就能通过 insertText(_:) 送进去。演示中,一个 UIImageView 子类接收相机文字,把它显示成图片标题。

详细内容

用内容类型过滤相机识别结果

02:18)相机输入的过滤依赖 TextContentTypeKeyboardType。这些属性存在于文本字段和文本视图上。已经为 AutoFill 设置过这些属性的 App,在 iOS 15 会自动获得相机输入过滤收益。

03:33)演示代码如下。

phone.keyboardType = .phonePad
phone.autocorrectionType = .no

address.textContentType = .fullStreetAddress

关键点:

  • phone.keyboardType = .phonePad 把电话字段的键盘设为电话键盘。
  • phone.autocorrectionType = .no 关闭自动纠错;没有自动纠错或预测候选时,iOS 会在候选栏提供快速打开相机的按钮。
  • address.textContentType = .fullStreetAddress 声明地址字段需要完整街道地址。
  • 相机输入读取这些属性后,会忽略不匹配的文字,减少用户手动框选。

02:53)本场演讲列出的可过滤类型有七种。前四种是已有类型:telephone number、full street address、URL、email。iOS 15 增加了 flight number、shipment tracking number、dates / times / durations。

phone.textContentType = .telephoneNumber
address.textContentType = .fullStreetAddress
website.textContentType = .URL
email.textContentType = .emailAddress
flight.textContentType = .flightNumber
tracking.textContentType = .shipmentTrackingNumber
eventTime.textContentType = .dateTime

关键点:

  • .telephoneNumber 让相机优先抓取电话号码。
  • .fullStreetAddress 让相机优先抓取完整地址。
  • .URL.emailAddress 适合从名片、海报、票据中提取网址和邮箱。
  • .flightNumber 面向旅行场景,适合登机牌、邮件打印件和行程单。
  • .shipmentTrackingNumber 面向物流场景,适合快递单和包裹标签。
  • .dateTime 面向活动时间、预约时间和持续时长。

把 Text from Camera 放到自己的入口里

04:21)系统编辑菜单里的 Text from Camera 入口不一定容易发现。演讲里的 Notes 字段需要二次点击才显示编辑菜单,候选栏还可能被预测文本占用。Apple 建议在想主动推广相机输入时,添加专用 launcher(启动入口)。

05:07)iOS 15 提供了 UIAction.captureTextFromCamera(responder:identifier:)

let textFromCamera = UIAction.captureTextFromCamera(responder: self.notes, identifier: nil)

关键点:

  • UIAction.captureTextFromCamera 创建一个系统动作。
  • responder: self.notes 指定识别出的文本插入到 notes 这个响应者。
  • identifier: nil 表示不提供自定义标识。
  • 这个 action 自带标题和图像;放入按钮或菜单时,系统会提供一致的显示样式。
  • 标题由系统本地化,开发者不需要自己写多语言文案。

05:41)它可以和其他相机相关动作放在同一个菜单里。

let textFromCamera = UIAction.captureTextFromCamera(responder: self.notes, identifier: nil)

let choosePhotoOrVideo = UIAction(title: "Choose Photo or Video") { _ in
    presentPhotoPicker()
}

let takePhotoOrVideo = UIAction(title: "Take Photo or Video") { _ in
    presentCamera()
}

let scanDocuments = UIAction(title: "Scan Documents") { _ in
    presentDocumentScanner()
}

let cameraMenu = UIMenu(children: [
    choosePhotoOrVideo,
    takePhotoOrVideo,
    scanDocuments,
    textFromCamera
])

let menuToolbarItem = UIBarButtonItem(
    title: nil,
    image: UIImage(systemName: "camera.badge.ellipsis"),
    primaryAction: nil,
    menu: cameraMenu
)

关键点:

  • textFromCamera 是系统相机文本输入动作。
  • choosePhotoOrVideotakePhotoOrVideoscanDocuments 代表 App 已有的相机或媒体动作。
  • UIMenu(children:) 把这些动作组合成一个菜单。
  • textFromCamera 放在同一组菜单里,用户能把它理解为相机能力的一部分。
  • UIBarButtonItem(..., menu:) 把菜单挂到工具栏按钮上。

显示入口前先检查可用性

06:29)演讲提醒,添加相机输入入口之前,要先检查 canPerformAction(_:withSender:)UIAction 内部会调用 UIResponder 上的 captureTextFromCamera,它和 cut、copy、paste 这类标准编辑动作类似,会受上下文影响。

let selector = #selector(UIResponder.captureTextFromCamera(_:))

if notes.canPerformAction(selector, withSender: nil) {
    let textFromCamera = UIAction.captureTextFromCamera(responder: notes, identifier: nil)
    cameraMenuItems.append(textFromCamera)
}

关键点:

  • #selector(UIResponder.captureTextFromCamera(_:)) 表示相机文本输入对应的 responder 动作。
  • notes.canPerformAction(selector, withSender: nil) 让系统判断当前上下文是否支持这个动作。
  • 返回 true 后再创建并展示入口,可以避免给用户一个不可用按钮。
  • 演讲列出的前提包括:设备硬件支持、响应者能处理文本插入、文本字段或文本视图可编辑、用户首选语言列表里至少有一种受支持语言。

让自定义视图接收相机输入

08:26)文本控件采用 UIKeyInput。相机识别出的文本通过 insertText(_:) 传给响应者。UITextInput 扩展自 UIKeyInput,还能通过 setMarkedText 提供待插入文本预览。预览是可选能力;只想接收最终文本时,实现 UIKeyInput 就够了。

09:59)演示里的图片标题视图这样实现。

class HeadlineImageView: UIImageView, UIKeyInput {
    var headlineLabel: UILabel = UILabel()
    var hasText: Bool = false

    override init(image: UIImage?) {
        super.init(image: image)
        initializeLabel()
    }

    func insertText(_ text: String) {
        headlineLabel.text = text
    }

    func deleteBackward() { }
}

关键点:

  • HeadlineImageView 继承 UIImageView,原始能力偏向图片展示。
  • UIKeyInput 让它可以作为文本输入的接收者。
  • headlineLabel 用来显示相机识别出的标题文字。
  • hasTextUIKeyInput 要求的属性;演讲里这个例子只需要相机输入,所以可以固定为 false
  • insertText(_:) 接收相机传入的字符串,并写入 headlineLabel.text
  • deleteBackward()UIKeyInput 要求的方法;这个例子不需要删除逻辑,所以方法体为空。

09:21)如果自定义响应者还想参与过滤,可以采用 UITextInputTraits,因为它提供 keyboardTypetextContentType 这类可选属性。

final class TrackingNumberView: UIView, UIKeyInput, UITextInputTraits {
    var label = UILabel()
    var hasText: Bool { label.text?.isEmpty == false }
    var textContentType: UITextContentType! = .shipmentTrackingNumber
    var keyboardType: UIKeyboardType = .asciiCapable

    func insertText(_ text: String) {
        label.text = text
    }

    func deleteBackward() {
        label.text = nil
    }
}

关键点:

  • UIKeyInput 负责接收插入文本。
  • UITextInputTraits 负责暴露输入特征。
  • textContentType = .shipmentTrackingNumber 告诉相机优先寻找快递单号。
  • keyboardType = .asciiCapable 给这个输入对象一个键盘类型提示。
  • hasText 根据当前标签内容返回状态。
  • deleteBackward() 清空标签内容,补齐基本删除行为。

核心启发

  1. 做什么:给酒店、民宿、会议签到表单添加纸面信息扫描。 为什么值得做:本场演讲展示了电话和地址过滤,正好覆盖入住单、确认函和前台资料。 怎么开始:给电话字段设置 .telephoneNumber.phonePad,给地址字段设置 .fullStreetAddress,再把 UIAction.captureTextFromCamera 放到表单工具栏。

  2. 做什么:做一个快递单号快速录入组件。 为什么值得做:iOS 15 新增 .shipmentTrackingNumber,相机可以从包裹标签里过滤出单号。 怎么开始:把输入控件的 textContentType 设为 .shipmentTrackingNumber,在控件旁放一个相机按钮,按钮触发 captureTextFromCamera

  3. 做什么:给旅行 App 增加航班号扫描。 为什么值得做:演讲明确提到 .flightNumber 适合旅行 App,用户可以从登机牌或行程单上录入航班。 怎么开始:为航班字段设置 .flightNumber,为日期字段设置 .dateTime,让相机分别过滤航班号和出发时间。

  4. 做什么:给照片封面添加现实文字标题。 为什么值得做:演示中的 HeadlineImageView 说明图片视图也能通过 UIKeyInput 接收相机文字。 怎么开始:创建 UIImageView 子类,添加 UILabel,实现 insertText(_:),再用 UIAction.captureTextFromCamera(responder: imageView, identifier: nil) 启动输入。

  5. 做什么:给活动海报收藏 App 添加时间识别。 为什么值得做:iOS 15 的相机过滤支持 dates、times 和 durations,适合从海报和传单录入活动时间。 怎么开始:把时间字段的 textContentType 设为 .dateTime,识别完成后再把字符串交给自己的日期解析逻辑。

关联 Session

评论

GitHub Issues · utterances