Files
MrleeAdmin/frontend/app/docs/mas-code-scan-iOS支持方案.md
T

720 lines
25 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# mas-code-scan iOS 端支持方案
> 文档状态:方案设计,尚未开始 iOS 原生实现。
>
> 目标是在不改变现有业务调用方式的前提下,为 `mas-code-scan` 增加 iOS App 支持,并与 Android 端尽量保持相同的功能、参数语义、回调顺序和错误码。
---
## 1. 背景与目标
当前 `mas-code-scan` 是 Android UTS API 插件,Android 原生层基于 CameraX + Google ML Kit 实现。对外 API 已沉淀在 `utssdk/interface.uts`,业务页面通过 `openCodeScan` 调用,因此具备扩展 iOS 实现的良好基础。
### 1.1 当前状态
| 项目 | 当前情况 |
| --- | --- |
| 对外 API | `openCodeScan(options)` |
| 公共类型 | `CodeScanOptions`、`CodeScanSuccess`、`CodeScanFail`、`ScanRegion`、`CodeScanStyle` |
| Android 实现 | `utssdk/app-android/`,CameraX + Google ML Kit |
| iOS 实现 | 暂无 `utssdk/app-ios/` 目录 |
| 业务调用 | `pages/scan/prototype.vue` 只需调用统一 API |
| 当前支持平台 | Android App(uni-app Vue2/Vue3) |
### 1.2 建设目标
1. **API 不变**:iOS 与 Android 均使用 `openCodeScan`,业务页面不增加平台分支。
2. **回调一致**:统一 `success`、`fail`、`complete` 的数据结构和触发时机。
3. **优先原生能力**:使用 iOS 系统框架,尽量不新增第三方识别库。
4. **功能分阶段交付**:先完成可真机使用的最小链路,再逐步补齐 Android 已有能力。
5. **平台差异显式处理**:对系统不支持或实现成本较高的能力,明确降级或延后,不以静默不生效代替。
### 1.3 非目标
本期方案不包含:
- 鸿蒙、H5、小程序、nvue 支持;
- 修改 Android 识别链路;
- 将扫码页改造为 Vue 组件;
- 为尚未经过真机验证的 iOS 能力提前修改 README 平台声明。
---
## 2. 总体架构
### 2.1 目录规划
在保持现有 Android 目录不变的基础上新增 iOS 实现:
```text
frontend/scanapp/uni_modules/mas-code-scan/
├── package.json
├── readme.md
├── changelog.md
└── utssdk/
├── interface.uts # Android / iOS 共用公开类型
├── unierror.uts # Android / iOS 共用错误对象
├── app-android/ # 现有 Android 实现
│ ├── index.uts
│ ├── CodeCaptureActivity.kt
│ ├── MultiCodePickView.kt
│ └── ScanFrameDecorView.kt
└── app-ios/ # 新增 iOS 实现
├── index.uts # UTS 桥接与回调编排
├── CodeCaptureViewController.swift
├── CodeScanner.swift
├── ScanOverlayView.swift
├── MultiCodePickView.swift
├── ScanStyleParser.swift
└── Info.plist # 或按 UTS iOS 配置规范声明权限
```
> `app-ios` 的最终配置文件和 Swift 文件参与编译方式,必须以当前 HBuilderX 版本的 UTS iOS 官方规范为准;开始编码前需要用官方最小示例验证目录结构。
### 2.2 分层职责
```text
业务页面
↓ openCodeScan(options)
UTS 平台入口(app-android/index.uts 或 app-ios/index.uts)
↓ 参数转换 / 生命周期 / 回调派发
原生扫码控制器
↓ 相机、识别、相册、交互
原生绘制层
↓ 扫描框、扫描线、多码点选、呼吸动画
```
- `interface.uts`:作为跨端 API 契约,不应写入 Android 或 iOS 私有字段。
- `app-ios/index.uts`:负责将 `CodeScanOptions` 转为原生配置,处理单次与连续扫码回调。
- `CodeCaptureViewController`:负责页面展示、权限、相机生命周期、工具栏和结果模式。
- `CodeScanner`:负责相机帧分析、Vision 识别、格式过滤、区域过滤和缩放策略。
- `ScanOverlayView`:负责 `scanStyle` 与 `scanRegion` 的视觉绘制。
- `MultiCodePickView`:负责多码定格、点选和绿色呼吸波纹。
---
## 3. iOS 技术选型
| 能力 | 推荐技术 | 选择理由 |
| --- | --- | --- |
| 相机预览与帧采集 | `AVFoundation` | 系统原生、稳定、无需新增依赖 |
| 条码识别 | `Vision` 的 `VNDetectBarcodesRequest` | 覆盖常见二维码与条形码,识别结果可获取码制和边界框 |
| 相册选图 | `PHPickerViewController` | iOS 14+ 可按用户选择读取图片,通常无需完整相册权限 |
| 低版本相册兼容 | `UIImagePickerController` | 仅在确认最低系统版本低于 iOS 14 时补充 |
| 扫描线动画 | `CAShapeLayer` + `CABasicAnimation` | 使用 Core Animation,避免定时器逐帧重绘 |
| 多码呼吸动画 | `CAShapeLayer` + `CABasicAnimation` | 与 Android 绿点呼吸波纹实现保持视觉一致 |
| 手势缩放 | `UIPinchGestureRecognizer` | 对应 Android 双指捏合 |
| 震动反馈 | `UIImpactFeedbackGenerator` | 适配 iOS 系统触感反馈 |
### 3.1 不引入第三方扫码库
首版不建议接入 ZXing、ML Kit iOS 等第三方库,原因:
- 系统 `Vision` 已能覆盖主要码制;
- 避免 CocoaPods/SPM 与 UTS iOS 构建的额外集成风险;
- 减少安装包和维护成本;
- 便于优先验证 UTS + Swift 桥接与相机链路。
若后续测试确认某些业务关键码制在 `Vision` 上识别率不足,再单独评估第三方引擎,不在首版预置依赖。
---
## 4. 公共 API 与行为约束
### 4.1 保持现有 API
现有 `CodeScanOptions` 不新增必填字段,iOS 端识别并实现以下参数:
| 参数 | iOS 首版目标 | 说明 |
| --- | --- | --- |
| `formats` | 支持 | 映射到 Vision 支持的 symbology;不支持值忽略 |
| `vibrate` | 支持 | 识别成功后触发轻触感反馈 |
| `fromAlbum` | 支持 | 打开扫码页后直接进入相册选择 |
| `scanRegion` | 支持 | 控制实时识别区域与扫描框;相册识图不受限制 |
| `scanStyle` | 支持 | 控制遮罩、边框、扫描线及动画 |
| `resultMode` | 支持 | `immediate`、`confirm`、`pause` |
| `continuous` | 支持 | 多次 `success`,关闭时 `complete` |
| `showPauseButton` | 支持 | 仅连续扫码时展示暂停/继续控制 |
| `torch: 'off'` | 支持 | 默认关闭 |
| `torch: 'on'` | 支持 | 设备支持时尝试开启 |
| `smartZoom` | 第二阶段 | 首版可读取但不自动变焦,后续补齐 |
| `torch: 'auto'` | 第二阶段 | 需要基于视频帧亮度策略验证 |
### 4.2 成功数据
iOS 识别结果必须统一转换为:
```ts
{
content: string,
format: string,
fromAlbum: boolean
}
```
格式名称与 Android 对齐,至少包括:
```text
QR_CODE
EAN_13
EAN_8
UPC_A
UPC_E
CODE_128
CODE_39
CODE_93
ITF
CODABAR
PDF417
AZTEC
DATA_MATRIX
```
当 Vision 不支持某个 Android 已支持的格式时:
1. `formats` 中的该值不参与 Vision 请求;
2. 不因单个非法或不支持值使整个调用失败;
3. 若过滤后没有任何 iOS 支持的格式,可按全格式或返回 `9021004`,最终策略需在编码前固定,并同步文档;
4. 推荐与 Android 保持一致:过滤为空时退回全格式识别。
### 4.3 回调一致性
| 场景 | `success` | `fail` | `complete` |
| --- | --- | --- | --- |
| 单次扫码成功 | 调用一次 | 不调用 | 紧随成功调用一次,参数为成功结果 |
| 单次取消/关闭 | 不调用 | `9021003` | 紧随失败调用一次 |
| 相机权限拒绝 | 不调用 | `9021002` | 紧随失败调用一次 |
| 无有效扫码结果的异常结束 | 不调用 | `9021004` | 紧随失败调用一次 |
| 连续扫码单次命中 | 每次命中调用 | 不调用 | 页面未关闭前不调用 |
| 连续扫码关闭且已有命中 | 不再调用 | 不调用 | `complete({ closed: true, hitCount })` |
| 连续扫码关闭且无命中 | 不调用 | `9021003` | 紧随失败调用一次 |
所有业务回调必须在主线程派发,控制器关闭、权限回调和异步识别完成之间要有会话状态保护,确保不会重复回调。
### 4.4 错误码复用
继续使用 `unierror.uts` 中的错误码:
| 错误码 | 语义 | iOS 对应场景 |
| --- | --- | --- |
| `9021001` | 当前无法获取页面 Activity | 无法获取当前 `UIViewController` 或不能展示扫码页 |
| `9021002` | 相机权限被拒绝 | `.denied` / `.restricted` 或用户拒绝授权 |
| `9021003` | 用户取消扫码 | 点击关闭、系统返回或取消图片选择 |
| `9021004` | 未获取到有效的扫码结果 | 扫码处理异常结束、相册图片无有效条码后由调用方选择结束 |
---
## 5. 核心功能设计
### 5.1 相机权限与页面生命周期
流程:
```text
openCodeScan
↓
获取当前 UIViewController
↓
检查 AVCaptureDevice 相机权限
├─ 已授权 → 初始化扫码控制器与会话
├─ 未决定 → 请求授权 → 成功后初始化
└─ 拒绝/受限 → fail(9021002) → complete
```
控制器生命周期要求:
- 页面展示时创建并启动 `AVCaptureSession`;
- 页面进入后台时暂停会话;
- 页面恢复前台时,未完成且未暂停时恢复会话;
- 页面关闭、成功返回或发生不可恢复错误时停止会话;
- 释放 `AVCaptureVideoDataOutput` 委托、图片引用、动画图层和 UTS 回调引用;
- 防止异步 Vision 回调在页面关闭后再提交结果。
### 5.2 实时扫码链路
```text
AVCaptureVideoDataOutput
↓ CMSampleBuffer
Vision VNDetectBarcodesRequest
↓ VNBarcodeObservation 列表
formats 过滤
↓
scanRegion 区域过滤
↓
单码 / 多码 / 连续扫码 / 结果模式处理
```
性能策略:
- 使用串行 `DispatchQueue` 处理 Vision 请求,避免同一时间处理多帧;
- 在上一帧识别尚未结束时丢弃新帧;
- 多码定格、确认弹窗、暂停状态、页面关闭后停止提交识别请求;
- 连续扫码需按 `content + format` 做短时间去重,建议冷却窗口 `800ms ~ 1200ms`;
- 不在 Vision 回调线程直接更新 UIKit,统一回主线程更新界面。
### 5.3 formats 映射
新增 iOS 私有映射表:
```text
QR_CODE → VNBarcodeSymbologyQR
EAN_13 → VNBarcodeSymbologyEAN13
EAN_8 → VNBarcodeSymbologyEAN8
UPC_A → VNBarcodeSymbologyUPCE(需以实际 Vision 结果确认映射)
UPC_E → VNBarcodeSymbologyUPCE
CODE_128 → VNBarcodeSymbologyCode128
CODE_39 → VNBarcodeSymbologyCode39
CODE_93 → VNBarcodeSymbologyCode93
ITF → VNBarcodeSymbologyI2of5
CODABAR → VNBarcodeSymbologyCodabar
PDF417 → VNBarcodeSymbologyPDF417
AZTEC → VNBarcodeSymbologyAztec
DATA_MATRIX → VNBarcodeSymbologyDataMatrix
```
> `VNBarcodeSymbology` 的实际可用范围与命名随 iOS SDK 变化。开发时必须以目标 Xcode / iOS SDK 编译结果为准,不能假定每个 Android 格式都一一对应。
### 5.4 scanRegion 坐标转换与过滤
`scanRegion` 是业务 API 中最容易出现跨平台差异的部分,iOS 必须确保“画面上显示的扫描框”与“实际允许识别的区域”一致。
处理步骤:
1. 根据 `width`、`height`、`centerX`、`centerY` 计算预览层的扫描框 `CGRect`;
2. 使用 `AVCaptureVideoPreviewLayer` 将 Vision 的归一化 `boundingBox` 转换为 UIKit 坐标;
3. 判断条码边界框中心点是否在扫描框中;
4. 仅保留区域内条码;
5. `fullScreen: true` 时不绘制扫描框且不进行区域过滤;
6. 相册识图跳过 `scanRegion` 过滤,保持 Android 行为。
验证重点:
- Vision 坐标原点与 UIKit 坐标原点不同;
- `.resizeAspectFill` 会产生裁切偏移;
- 前置镜像、横竖屏和刘海安全区可能影响坐标;
- 多码点选标记必须与真实条码中心重合。
### 5.5 scanStyle 绘制
`ScanOverlayView` 使用 `CAShapeLayer` 与 `CAGradientLayer` 绘制,配置语义与 Android 一致:
| 参数 | iOS 实现 |
| --- | --- |
| `showMask` | 显示/隐藏扫描框外遮罩层 |
| `showBeam` | 显示/隐藏扫描线图层 |
| `borderColor` | `CGColor` 边框颜色 |
| `maskColor` | 解析 `#RRGGBB`、`#AARRGGBB`、`rgba()` 后填充遮罩 |
| `beamColor` | 扫描线颜色 |
| `beamDuration` | 扫描线 `CABasicAnimation.duration`,限制 `100 ~ 60000ms` |
| `cornerStyle: 'corner'` | 绘制四角边框 |
| `cornerStyle: 'full'` | 绘制完整矩形边框 |
默认值必须沿用当前接口默认行为:
```ts
{
showMask: true,
showBeam: true,
borderColor: '#19be6b',
maskColor: 'rgba(0, 0, 0, 0.45)',
beamColor: '#12e880',
beamDuration: 1800,
cornerStyle: 'corner'
}
```
颜色解析建议单独实现 `ScanStyleParser`:
- 支持 `#RRGGBB`;
- 支持 `#AARRGGBB`;
- 支持 `rgba(r,g,b,a)`;
- 解析失败时回退默认色;
- 所有回退规则与 Android `readScanStyle()` 保持一致。
### 5.6 单码、多码和点选
#### 单码
单个有效条码按 `resultMode` 处理:
- `immediate`:停止识别并立即返回;
- `confirm`:暂停识别,弹出确认框;
- `pause`:暂停识别,显示继续扫描和确认返回操作。
#### 多码
同一帧出现两个及以上有效、有内容且有边界框的条码时:
1. 停止实时识别;
2. 抓取当前预览帧;
3. 显示定格画面和半透明遮罩;
4. 把各码的边界框映射到屏幕;
5. 在中心位置绘制点选标记;
6. 显示“已识别到多个码,请选择一个”提示;
7. 用户点击最近标记后提交对应条码结果。
`MultiCodePickView` 的标记视觉与 Android 对齐:
```text
绿色中心圆点
+ 白色圆形底
+ 绿色半透明外圈
+ 周期性呼吸扩散动画
```
建议使用 `CAKeyframeAnimation` 或 `CABasicAnimation` 同步变化外圈的 `transform.scale` 与 `opacity`,周期暂定 `1500ms`。
点击策略:
- 以点位中心为基准选择最近候选;
- 设置最小可点击半径,建议约 `28pt`;
- 无命中点时继续等待选择,不直接取消;
- 用户主动关闭时按取消流程处理。
### 5.7 相册识图
优先采用 `PHPickerViewController`:
```text
fromAlbum: true 或点击相册按钮
↓
系统图片选择器
↓
获取 UIImage / CGImage
↓
Vision VNDetectBarcodesRequest
↓
单码直接返回;多码进入点选
```
规则:
- 相册识图结果的 `fromAlbum` 固定为 `true`;
- 相册图片不受 `scanRegion` 限制;
- 用户取消图片选择:单次扫码按 `9021003` 处理;
- 图片中未识别到条码时,提示“未识别到有效条码”,并返回扫码页继续操作;
- 多码相册图片使用图片本身作为定格背景并进入点选。
### 5.8 连续扫码与暂停
`continuous: true` 时:
- 命中一次条码调用一次 `success`;
- 扫码页不关闭;
- 同一内容需通过短时冷却避免高频重复提交;
- `showPauseButton: true` 时展示暂停/继续按钮;
- 用户关闭时,若已有命中,则仅调用:
```ts
complete({ closed: true, hitCount })
```
- 用户关闭时,若没有命中,则按 `fail(9021003)` 后 `complete`;
- 多码点选确认后,在连续模式下应投递一次成功结果、清除定格层、恢复相机识别,而不是关闭控制器。
### 5.9 手电筒、缩放与震动
#### 手电筒
- 使用 `AVCaptureDevice.hasTorch` 判断设备能力;
- 使用 `lockForConfiguration()` + `torchMode` 变更状态;
- `off`:默认关闭;
- `on`:启动后尝试开启;
- 无手电筒设备隐藏或禁用页面按钮;
- 页面结束时关闭手电筒。
#### 手势缩放
- 使用 `UIPinchGestureRecognizer`;
- 映射至 `videoZoomFactor`;
- 限制在 `1.0 ... activeFormat.videoMaxZoomFactor` 的安全范围;
- 变焦期间禁止智能变焦抢占。
#### 智能变焦
作为第二阶段能力:
- 根据条码边界框占画面比例计算建议倍率;
- 设定最大尝试次数、最大倍率和平滑过渡;
- 检测到多个码时先恢复 `1.0x`,避免多码点选区域被放大裁切;
- 与手势缩放、连续扫码、定格状态互斥。
#### 震动
- `vibrate: true` 时使用 `UIImpactFeedbackGenerator(style: .light)`;
- 连续扫码每次实际成功投递时触发;
- 系统可能因“减少动态效果”、静音模式或设备策略弱化触感反馈,业务层不应依赖其作为唯一成功提示。
---
## 6. UTS 桥接设计
### 6.1 app-ios/index.uts 职责
`app-ios/index.uts` 负责:
1. 获取可展示扫码页的当前 `UIViewController`;
2. 校验当前是否已有扫码会话,避免重复打开;
3. 归一化 `CodeScanOptions` 默认值;
4. 创建并展示 `CodeCaptureViewController`;
5. 接收原生事件并转换为 `CodeScanSuccess` / `CodeScanFailImpl`;
6. 保证回调顺序和只执行一次的终止语义;
7. 连续扫码时维护 `hitCount`;
8. 控制器退出后解除事件与对象引用。
### 6.2 建议的原生事件模型
控制器向桥接层输出有限事件,不直接感知 UTS 回调:
```text
onResult(content, format, fromAlbum)
onCancel()
onPermissionDenied()
onFatalError()
onClosed(hitCount, hadHits)
```
桥接层统一决定:
- 单次成功:`success` → `complete`;
- 单次失败:`fail` → `complete`;
- 连续扫码:每次 `onResult` → `success`,关闭时再 `complete`;
- 已结束会话的后续事件全部丢弃。
### 6.3 会话状态
建议 iOS 桥接层维护:
```text
idle → presenting → scanning → paused / picking → finishing → finished
```
关键约束:
- `finished` 后不允许再次派发回调;
- 权限请求、Vision 回调、相册回调都需检查当前状态;
- 控制器 dismissal 完成后再释放强引用;
- 业务层再次调用时必须创建新会话,不复用旧回调。
---
## 7. 权限与配置
### 7.1 必需权限
iOS 必须声明:
```xml
<key>NSCameraUsageDescription</key>
<string>需要使用相机扫描二维码和条形码</string>
```
若采用 `PHPickerViewController` 且最低系统版本为 iOS 14,一般不需要完整相册访问权限。
若最终需要 `UIImagePickerController` 或直接读取照片库,则补充:
```xml
<key>NSPhotoLibraryUsageDescription</key>
<string>需要访问相册以识别图片中的二维码和条形码</string>
```
### 7.2 最低系统版本建议
建议首版目标:
```text
iOS 14+
```
理由:
- 可以使用 `PHPickerViewController`;
- Vision 条码能力更成熟;
- 降低图片权限和兼容逻辑复杂度。
若产品必须支持 iOS 13,需要增加相册选择兼容分支,并重新评估 Vision 可识别格式集合。
### 7.3 打包与调试
iOS 原生 UTS 代码不能通过 Web 预览验证,必须使用:
- 包含插件的 iOS 自定义调试基座;或
- iOS 云打包 / 本地原生打包产物;
- 真机设备与 Xcode 控制台。
模拟器不应作为相机扫码主验收环境;可用于页面布局、权限分支和相册图片识别的部分验证。
---
## 8. 分阶段实施计划
### 阶段 0:UTS iOS 可编译性验证
目标:确认当前 HBuilderX 与项目配置可编译 `app-ios` 原生实现。
任务:
- 创建最小 `app-ios/index.uts`;
- 验证获取当前 iOS 控制器与展示原生页面的官方方式;
- 验证 Swift 文件参与 UTS iOS 编译的目录和配置;
- 在 iPhone 真机执行一个最小“打开原生页并返回”示例;
- 固化项目实际约定后再开始完整扫码实现。
验收:iOS 自定义基座可编译并成功打开、关闭原生测试页面。
### 阶段 1:最小可用扫码
范围:
- 相机权限;
- 后置相机预览;
- Vision 单码实时识别;
- `success`、`fail`、`complete`;
- 用户关闭;
- 常见二维码与条形码格式。
验收:iPhone 真机可扫描 QR Code 和 Code 128,并按既定顺序回调。
### 阶段 2:跨端核心功能对齐
范围:
- `formats`;
- `scanRegion`;
- `scanStyle`;
- 相册识图;
- 多码定格点选;
- 绿色呼吸波纹;
- `resultMode`;
- `vibrate`。
验收:Android 与 iOS 对同一配置的显示、区域过滤和数据结构一致。
### 阶段 3:会话与相机增强
范围:
- `continuous`;
- `showPauseButton`;
- `torch: 'off' | 'on'`;
- 手势缩放;
- 连续扫码去重与关闭结算。
验收:连续扫码有命中和无命中关闭时的回调行为与 Android 一致。
### 阶段 4:高级策略
范围:
- `smartZoom`;
- `torch: 'auto'`;
- 复杂机型上的性能优化;
- 必要时评估第三方识别引擎。
验收:在目标机型上无明显误触发、频繁跳变、发热或回调重复问题。
---
## 9. 验收矩阵
### 9.1 功能验收
| 场景 | 预期 |
| --- | --- |
| 单个二维码 | 返回 `QR_CODE`、`fromAlbum: false` |
| 单个 Code 128 | 返回 `CODE_128` |
| formats 过滤 | 仅接受指定码制 |
| 全屏识别 | 不显示扫描框,整画面可识别 |
| 自定义区域 | 框外条码不返回,框内条码正常返回 |
| 相册单码 | 返回 `fromAlbum: true` |
| 相册多码 | 显示定格图片与多个可点击标记 |
| 实时多码 | 显示定格画面、提示语和绿色呼吸点 |
| confirm 模式 | 确认后返回,取消后继续识别 |
| pause 模式 | 可继续扫描或确认结果 |
| 连续扫码 | 多次 success,关闭后返回正确 hitCount |
| 连续扫码无命中关闭 | fail `9021003` 后 complete |
| 相机权限拒绝 | fail `9021002` 后 complete |
| 无手电筒设备 | 不崩溃,按钮隐藏或禁用 |
### 9.2 机型与系统验收
至少覆盖:
- iPhone 小屏机型;
- 刘海屏机型;
- 灵动岛机型;
- 无手电筒或手电筒不可用场景;
- iOS 14、16、17 及项目实际要求的最新系统;
- 深色模式;
- 权限首次授权、拒绝、设置页重新授权;
- 前后台切换、来电或控制中心打断后恢复;
- 快速打开、关闭、再次打开扫码页。
### 9.3 性能与稳定性验收
- 连续识别 5 分钟不出现明显内存持续增长;
- 多次进入、退出扫码页后相机可再次正常打开;
- 多码定格帧不会造成明显卡顿或黑屏;
- 页面关闭后不再收到 `success`;
- 不发生重复 `complete` 或 success/fail 同时触发;
- 扫描框与实际识别区域、点选标记位置对齐。
---
## 10. 风险与应对
| 风险 | 影响 | 应对措施 |
| --- | --- | --- |
| UTS iOS 原生目录/Swift 桥接规则与预期不同 | 无法编译或调用 | 阶段 0 使用官方最小示例先验证,不直接大规模开发 |
| Vision 支持的码制与 Android 不完全一致 | formats 行为差异 | 建立格式能力矩阵,过滤不支持项并在 README 明示 |
| Vision 到预览层坐标转换错误 | scanRegion、点选位置偏移 | 单独编写坐标转换测试清单,覆盖 Aspect Fill、旋转和安全区 |
| 定格帧转换开销高 | 多码时卡顿、内存上涨 | 限制图像分辨率,及时释放 `CGImage` / `UIImage`,仅保留一帧 |
| 异步回调竞争 | 重复回调、页面关闭后回调 | 使用会话状态机和主线程串行化终止事件 |
| Torch / zoom 在机型上差异大 | 功能不稳定 | 将 `auto` 与 smartZoom 放到后续阶段,先实现基础能力 |
| iOS 权限文案或 Info.plist 缺失 | 审核或运行时崩溃 | 将权限配置纳入构建前检查和真机回归 |
---
## 11. 代码与文档更新清单
### 11.1 开发完成后需要修改
| 文件 | 修改内容 |
| --- | --- |
| `utssdk/app-ios/*` | 新增 iOS UTS 桥接和 Swift 原生实现 |
| `package.json` | `displayName`、描述、关键字、`platforms.client.uni-app.app.ios` 改为支持 |
| `readme.md` | 支持平台改为 Android / iOS;补充 iOS 权限、最低版本和格式差异说明 |
| `changelog.md` | 记录 iOS 首次支持及功能范围 |
| `pages/scan/prototype.vue` | 将 Android 专属提示改为 Android / iOS App 提示 |
### 11.2 开发完成前不得修改
以下内容必须等 iOS 自定义基座成功编译且完成真机验收后再调整:
- README 的“支持 iOS”声明;
- `package.json` 中的 `ios: '√'`;
- 业务页面中“Android 专属”的运行提示;
- changelog 中的正式 iOS 发布记录。
---
## 12. 推荐实施顺序
1. 确认项目的 HBuilderX 版本、Xcode 版本和最低 iOS 版本;
2. 用官方 UTS iOS 最小原生页面验证 `app-ios` 目录与 Swift 编译方式;
3. 实现相机权限 + 单码 QR 实时识别闭环;
4. 固化 `success` / `fail` / `complete` 的 iOS 回调行为;
5. 加入 formats、scanRegion 和 scanStyle;
6. 加入相册识图和多码定格点选;
7. 加入连续扫码、暂停、手电筒和手势缩放;
8. 最后实现 smartZoom、auto torch 等设备敏感能力;
9. 真机回归通过后更新插件元信息、README、changelog 和演示页文案。
---
## 13. 结论
推荐采用 **AVFoundation + Vision + PHPickerViewController** 的原生 iOS 实现方案,并在 `utssdk/app-ios/` 中增加独立平台入口。现有 `interface.uts`、`unierror.uts` 和 `openCodeScan` 调用方式继续作为跨端契约,Android 代码无需重构。
实施上应先完成 iOS UTS 编译与原生页面呈现验证,再逐步建立单码扫码、多码点选、相册识图和连续扫码能力。这样能将最大的构建与坐标转换风险前置,同时避免在未验证前修改对外平台支持声明。