Swift UITextField一键唤起系统Scan Text功能方案问询
实现点击UITextField直接唤起系统Scan Text功能的方案
核心思路
要跳过繁琐的五步操作直接唤起系统Scan Text,有两种主要方向:要么直接触发系统内置的Scan Text动作,要么基于系统框架自定义接近原生体验的扫描界面。
方案一:直接触发系统Scan Text动作(推荐,iOS 15+)
系统的Scan Text是文本输入框菜单的一部分,我们可以在UITextField被点击时,自动触发这个内置动作,无需弹出键盘。
代码实现(Swift)
import UIKit class VINInputTextField: UITextField { override func awakeFromNib() { super.awakeFromNib() // 替换默认键盘为空视图,避免弹出键盘 inputView = UIView(frame: .zero) } override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { super.touchesBegan(touches, with: event) // 查找系统的Scan Text动作并触发 triggerSystemScanTextAction() } private func triggerSystemScanTextAction() { guard let contextMenu = textInputContextMenu?.menu else { // fallback:系统不支持时弹出键盘 becomeFirstResponder() return } // 通过本地化字符串匹配动作(避免硬编码标题导致多语言失效) let scanTextTitle = NSLocalizedString("UIMenuItemScanText", comment: "") for element in contextMenu.children { guard let action = element as? UIAction, action.title == scanTextTitle else { continue } action.perform(withSender: self) return } // 未找到动作时的 fallback becomeFirstResponder() } }
注意事项
- 该方案依赖系统文本菜单的结构,需测试不同iOS版本的兼容性。
- 使用系统本地化字符串
UIMenuItemScanText确保多语言环境下能正确匹配动作。
方案二:自定义接近原生的扫描界面(兼容iOS 13+)
如果无法直接调用系统Scan Text,可基于VisionKit和Vision框架自定义界面,还原原生体验:
实现步骤
- 创建扫描控制器:用
VNDocumentCameraViewController获取相机画面,自定义导航栏按钮替换默认快门。 - 实时文本识别:用
Vision框架的VNRecognizeTextRequest实时识别VIN码,添加捕获框预览。 - 关联UITextField:点击输入框时直接弹出自定义扫描控制器,识别完成后回填文本。
核心代码片段
import UIKit import VisionKit import Vision class VINScanController: UIViewController, VNDocumentCameraViewControllerDelegate { private var onCompletion: ((String?) -> Void)? static func show(from parent: UIViewController, completion: @escaping (String?) -> Void) { let controller = VINScanController() controller.onCompletion = completion let docCamera = VNDocumentCameraViewController() docCamera.delegate = controller // 自定义导航栏按钮 docCamera.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Cancel", style: .plain, target: controller, action: #selector(cancelScan)) docCamera.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Insert", style: .done, target: controller, action: #selector(insertScanResult)) parent.present(UINavigationController(rootViewController: docCamera), animated: true) } @objc private func cancelScan() { dismiss(animated: true) { self.onCompletion?(nil) } } @objc private func insertScanResult() { // 这里需要结合实时识别获取当前识别的VIN码 // 简化示例:假设已获取到识别文本 dismiss(animated: true) { self.onCompletion?("识别到的VIN码") } } // MARK: - VNDocumentCameraDelegate func documentCameraViewController(_ controller: VNDocumentCameraViewController, didFinishWith scan: VNDocumentCameraScan) { // 处理拍照后的识别逻辑 let image = scan.imageOfPage(at: 0) let request = VNRecognizeTextRequest { [weak self] _, error in guard let observations = request.results as? [VNRecognizedTextObservation], error == nil else { self?.onCompletion?(nil) return } let text = observations.compactMap { $0.topCandidates(1).first?.string }.joined() // 过滤17位VIN码 let vinRegex = try! NSRegularExpression(pattern: "^[A-HJ-NPR-Z0-9]{17}$") if let match = vinRegex.firstMatch(in: text, range: NSRange(text.startIndex..., in: text)) { let vin = String(text[Range(match.range, in: text)!]) self?.onCompletion?(vin) } else { self?.onCompletion?(text) } } request.recognitionLevel = .accurate try! VNImageRequestHandler(cgImage: image.cgImage!).perform([request]) } } // 在UITextField子类中调用 class VINInputTextField: UITextField, UITextFieldDelegate { override func awakeFromNib() { super.awakeFromNib() delegate = self } func textFieldDidBeginEditing(_ textField: UITextField) { textField.resignFirstResponder() VINScanController.show(from: window!.rootViewController!) { [weak self] vin in self?.text = vin } } }
补充:添加实时捕获框
要在预览中显示识别文本的捕获框,需要结合AVCaptureSession获取实时视频帧,通过Vision识别后在预览层绘制边框,这部分可以参考Vision框架的实时文本识别示例。
全局扩展方案
如果需要在多处UITextField使用该功能,可以创建扩展:
extension UITextField { func enableVINScanOnTap() { let tapGesture = UITapGestureRecognizer(target: self, action: #selector(triggerVINScan)) addGestureRecognizer(tapGesture) inputView = UIView(frame: .zero) } @objc private func triggerVINScan() { // 调用方案一的触发逻辑或方案二的扫描控制器 VINScanController.show(from: window!.rootViewController!) { [weak self] vin in self?.text = vin } } }
在Storyboard中,只需给目标UITextField添加enableVINScanOnTap()调用即可。
内容的提问来源于stack exchange,提问作者Ben Hill
相关产品推荐
相关产品推荐

