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.
MyQRCode/docs/TORCH_FEATURE_README.md

8.1 KiB

手电筒功能添加说明

🎯 功能概述

ScannerView 添加了手电筒控制功能,用户可以通过工具栏按钮打开/关闭手电筒,提升在低光环境下的扫描体验。

🔧 主要修改内容

1. ScannerViewModel.swift 的修改

新增状态变量

@Published var isTorchOn = false  // 手电筒开关状态
private var videoDevice: AVCaptureDevice?  // 视频设备引用

保存视频设备引用

private func setupCaptureSession() {
    // ... 现有代码 ...
    
    guard let videoCaptureDevice = AVCaptureDevice.default(for: .video) else {
        showAlert = true
        return
    }
    
    // 保存视频设备引用,用于手电筒控制
    videoDevice = videoCaptureDevice
    
    // ... 现有代码 ...
}

手电筒控制方法

// 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 的修改

工具栏布局更新

.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 {
            // ... 现有代码 ...
        }
    }
}

生命周期管理

.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. 设备兼容性

  • 不是所有设备都支持手电筒
  • 需要检查 hasTorchisTorchAvailable 属性
  • 在模拟器上可能无法测试手电筒功能

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'

修复方案

// ❌ 错误的关闭方式
try device.setTorchModeOn(level: 0.0)

// ✅ 正确的关闭方式
device.torchMode = .off

修复后的代码

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

手电筒功能将显著提升用户在低光环境下的扫描体验,特别是在夜间或光线不足的室内环境中。🎉