You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
288 lines
8.1 KiB
288 lines
8.1 KiB
# 手电筒功能添加说明
|
|
|
|
## 🎯 功能概述
|
|
|
|
为 `ScannerView` 添加了手电筒控制功能,用户可以通过工具栏按钮打开/关闭手电筒,提升在低光环境下的扫描体验。
|
|
|
|
## 🔧 主要修改内容
|
|
|
|
### 1. **ScannerViewModel.swift 的修改**
|
|
|
|
#### 新增状态变量
|
|
```swift
|
|
@Published var isTorchOn = false // 手电筒开关状态
|
|
private var videoDevice: AVCaptureDevice? // 视频设备引用
|
|
```
|
|
|
|
#### 保存视频设备引用
|
|
```swift
|
|
private func setupCaptureSession() {
|
|
// ... 现有代码 ...
|
|
|
|
guard let videoCaptureDevice = AVCaptureDevice.default(for: .video) else {
|
|
showAlert = true
|
|
return
|
|
}
|
|
|
|
// 保存视频设备引用,用于手电筒控制
|
|
videoDevice = videoCaptureDevice
|
|
|
|
// ... 现有代码 ...
|
|
}
|
|
```
|
|
|
|
#### 手电筒控制方法
|
|
```swift
|
|
// MARK: - 手电筒控制
|
|
|
|
/// 检查设备是否支持手电筒
|
|
var isTorchAvailable: Bool {
|
|
guard let device = videoDevice else { return false }
|
|
return device.hasTorch && device.isTorchAvailable
|
|
}
|
|
|
|
/// 切换手电筒状态
|
|
func toggleTorch() {
|
|
guard let device = videoDevice else {
|
|
logWarning("❌ 没有可用的视频设备", className: "ScannerViewModel")
|
|
return
|
|
}
|
|
|
|
guard device.hasTorch && device.isTorchAvailable else {
|
|
logWarning("❌ 设备不支持手电筒", className: "ScannerViewModel")
|
|
return
|
|
}
|
|
|
|
do {
|
|
try device.lockForConfiguration()
|
|
|
|
if isTorchOn {
|
|
// 关闭手电筒
|
|
try device.setTorchModeOn(level: 0.0)
|
|
isTorchOn = false
|
|
logInfo("🔦 手电筒已关闭", className: "ScannerViewModel")
|
|
} else {
|
|
// 打开手电筒
|
|
try device.setTorchModeOn(level: 1.0)
|
|
isTorchOn = true
|
|
logInfo("🔦 手电筒已打开", className: "ScannerViewModel")
|
|
}
|
|
|
|
device.unlockForConfiguration()
|
|
|
|
} catch {
|
|
logError("❌ 手电筒控制失败: \(error.localizedDescription)", className: "ScannerViewModel")
|
|
device.unlockForConfiguration()
|
|
}
|
|
}
|
|
|
|
/// 关闭手电筒
|
|
func turnOffTorch() {
|
|
guard let device = videoDevice else { return }
|
|
|
|
do {
|
|
try device.lockForConfiguration()
|
|
try device.setTorchModeOn(level: 0.0)
|
|
isTorchOn = false
|
|
device.unlockForConfiguration()
|
|
logInfo("🔦 手电筒已关闭", className: "ScannerViewModel")
|
|
} catch {
|
|
logError("❌ 关闭手电筒失败: \(error.localizedDescription)", className: "ScannerViewModel")
|
|
device.unlockForConfiguration()
|
|
}
|
|
}
|
|
```
|
|
|
|
### 2. **ScannerView.swift 的修改**
|
|
|
|
#### 工具栏布局更新
|
|
```swift
|
|
.toolbar {
|
|
ToolbarItem(placement: .navigationBarLeading) {
|
|
// 手电筒按钮 - 只在相机权限已授权时显示
|
|
if scannerViewModel.cameraAuthorizationStatus == .authorized && scannerViewModel.isTorchAvailable {
|
|
Button(action: {
|
|
logInfo("🔦 用户点击手电筒按钮", className: "ScannerView")
|
|
|
|
// 添加触觉反馈
|
|
let impactFeedback = UIImpactFeedbackGenerator(style: .medium)
|
|
impactFeedback.impactOccurred()
|
|
|
|
scannerViewModel.toggleTorch()
|
|
}) {
|
|
Image(systemName: scannerViewModel.isTorchOn ? "bolt.fill" : "bolt")
|
|
.font(.system(size: 18, weight: .semibold))
|
|
.foregroundColor(scannerViewModel.isTorchOn ? .yellow : .blue)
|
|
}
|
|
}
|
|
}
|
|
|
|
ToolbarItem(placement: .navigationBarTrailing) {
|
|
// 重新扫描按钮 - 只在预览暂停状态时显示
|
|
if showPreviewPause {
|
|
// ... 现有代码 ...
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
#### 生命周期管理
|
|
```swift
|
|
.onDisappear {
|
|
scannerViewModel.stopScanning()
|
|
// 确保退出时关闭手电筒
|
|
if scannerViewModel.isTorchOn {
|
|
scannerViewModel.turnOffTorch()
|
|
}
|
|
}
|
|
```
|
|
|
|
## 🚀 功能特性
|
|
|
|
### 1. **智能显示**
|
|
- 手电筒按钮只在设备支持手电筒时显示
|
|
- 只在相机权限已授权时显示
|
|
- 避免在不支持的设备上显示无效按钮
|
|
|
|
### 2. **状态指示**
|
|
- 使用闪电图标表示手电筒状态:
|
|
- `bolt`: 手电筒关闭状态(蓝色)
|
|
- `bolt.fill`: 手电筒开启状态(黄色)
|
|
- 颜色变化提供直观的状态反馈
|
|
|
|
### 3. **安全控制**
|
|
- 设备配置锁定/解锁确保安全
|
|
- 错误处理和日志记录
|
|
- 应用退出时自动关闭手电筒
|
|
|
|
### 4. **用户体验**
|
|
- 触觉反馈增强交互体验
|
|
- 按钮位置合理(导航栏左侧)
|
|
- 图标大小和样式符合系统设计
|
|
|
|
## 📱 界面布局
|
|
|
|
### 工具栏布局
|
|
```
|
|
[🔦 手电筒] ← 扫描器 → [🔄 重新扫描]
|
|
```
|
|
|
|
- **左侧**: 手电筒按钮(始终显示,如果支持)
|
|
- **中间**: 导航标题
|
|
- **右侧**: 重新扫描按钮(仅在预览暂停时显示)
|
|
|
|
### 按钮状态
|
|
- **关闭状态**: 蓝色闪电图标 + 关闭填充
|
|
- **开启状态**: 黄色闪电图标 + 开启填充
|
|
|
|
## 🧪 测试要点
|
|
|
|
### 1. **功能测试**
|
|
- ✅ 手电筒按钮在支持的设备上正确显示
|
|
- ✅ 点击按钮正确切换手电筒状态
|
|
- ✅ 手电筒实际开启/关闭
|
|
- ✅ 状态指示正确更新
|
|
|
|
### 2. **兼容性测试**
|
|
- ✅ 在不支持手电筒的设备上不显示按钮
|
|
- ✅ 在相机权限未授权时不显示按钮
|
|
- ✅ 在不同设备上正常工作
|
|
|
|
### 3. **安全性测试**
|
|
- ✅ 应用退出时手电筒自动关闭
|
|
- ✅ 设备配置正确锁定/解锁
|
|
- ✅ 错误情况下的安全处理
|
|
|
|
### 4. **用户体验测试**
|
|
- ✅ 触觉反馈正常工作
|
|
- ✅ 按钮响应及时
|
|
- ✅ 图标和颜色变化清晰
|
|
|
|
## 🔍 技术实现细节
|
|
|
|
### 1. **AVFoundation 集成**
|
|
- 使用 `AVCaptureDevice` 控制手电筒
|
|
- 设备配置锁定确保安全操作
|
|
- 错误处理和资源管理
|
|
|
|
### 2. **状态管理**
|
|
- `@Published var isTorchOn` 提供响应式状态
|
|
- 状态变化自动更新 UI
|
|
- 生命周期管理确保状态一致性
|
|
|
|
### 3. **权限检查**
|
|
- 检查设备硬件支持
|
|
- 检查相机权限状态
|
|
- 条件渲染避免无效操作
|
|
|
|
### 4. **错误处理**
|
|
- 完整的错误捕获和日志记录
|
|
- 设备配置解锁确保资源释放
|
|
- 用户友好的错误提示
|
|
|
|
## 🚨 注意事项
|
|
|
|
### 1. **设备兼容性**
|
|
- 不是所有设备都支持手电筒
|
|
- 需要检查 `hasTorch` 和 `isTorchAvailable` 属性
|
|
- 在模拟器上可能无法测试手电筒功能
|
|
|
|
### 2. **权限管理**
|
|
- 手电筒功能需要相机权限
|
|
- 权限变化时按钮状态需要更新
|
|
- 权限被拒绝时的处理
|
|
|
|
### 3. **电池管理**
|
|
- 手电筒会消耗较多电量
|
|
- 建议在不需要时及时关闭
|
|
- 应用退出时自动关闭
|
|
|
|
### 4. **用户体验**
|
|
- 手电筒开启时提供视觉反馈
|
|
- 考虑添加亮度调节功能
|
|
- 在低光环境下的使用提示
|
|
|
|
## 🐛 Bug修复
|
|
|
|
### 问题描述
|
|
初始实现中使用 `setTorchModeOn(level: 0.0)` 来关闭手电筒,这会导致崩溃:
|
|
```
|
|
Terminating app due to uncaught exception 'NSInvalidArgumentException',
|
|
reason: '*** -[AVCaptureDevice setTorchModeOnWithLevel:error:] The passed torchLevel 0.000000 is invalid'
|
|
```
|
|
|
|
### 修复方案
|
|
```swift
|
|
// ❌ 错误的关闭方式
|
|
try device.setTorchModeOn(level: 0.0)
|
|
|
|
// ✅ 正确的关闭方式
|
|
device.torchMode = .off
|
|
```
|
|
|
|
### 修复后的代码
|
|
```swift
|
|
if isTorchOn {
|
|
// 关闭手电筒
|
|
device.torchMode = .off
|
|
isTorchOn = false
|
|
logInfo("🔦 手电筒已关闭", className: "ScannerViewModel")
|
|
} else {
|
|
// 打开手电筒
|
|
try device.setTorchModeOn(level: 1.0)
|
|
isTorchOn = true
|
|
logInfo("🔦 手电筒已打开", className: "ScannerViewModel")
|
|
}
|
|
```
|
|
|
|
## 📊 功能总结
|
|
|
|
通过这次修改,我们成功添加了手电筒控制功能:
|
|
|
|
1. **功能完整**: 支持手电筒的开启、关闭和状态切换
|
|
2. **界面友好**: 直观的图标和颜色状态指示
|
|
3. **安全可靠**: 完整的错误处理和资源管理
|
|
4. **用户体验**: 触觉反馈和智能显示逻辑
|
|
5. **稳定性**: 修复了关闭手电筒时的崩溃bug
|
|
|
|
手电筒功能将显著提升用户在低光环境下的扫描体验,特别是在夜间或光线不足的室内环境中。🎉 |