# 手电筒功能添加说明 ## 🎯 功能概述 为 `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 手电筒功能将显著提升用户在低光环境下的扫描体验,特别是在夜间或光线不足的室内环境中。🎉