import SwiftUI import AVFoundation import AudioToolbox import Combine // MARK: - 主扫描视图 struct ScannerView: View { @StateObject private var scannerViewModel = ScannerViewModel() @State private var showPreviewPause = false @State private var selectedScanningStyle: ScanningLineStyle = .modern @State private var screenOrientation = UIDevice.current.orientation @State private var previewLayer: AVCaptureVideoPreviewLayer? var body: some View { ZStack { // 相机权限检查 if scannerViewModel.cameraAuthorizationStatus == .authorized { // 相机预览层 CameraPreviewView(session: scannerViewModel.captureSession, previewLayer: $previewLayer) .ignoresSafeArea() // 扫描界面覆盖层 ScanningOverlayView( showPreviewPause: showPreviewPause, selectedStyle: $selectedScanningStyle, detectedCodesCount: scannerViewModel.detectedCodes.count ) // 条码位置标记覆盖层 if showPreviewPause && !scannerViewModel.detectedCodes.isEmpty { CodePositionOverlay( detectedCodes: scannerViewModel.detectedCodes, previewLayer: previewLayer, onCodeSelected: handleCodeSelection ) } // 测试按钮(调试用) if showPreviewPause && scannerViewModel.detectedCodes.count == 1 { TestAutoSelectButton( detectedCode: scannerViewModel.detectedCodes[0], onSelect: handleCodeSelection ) } } else { // 权限未授权时的UI CameraPermissionView( authorizationStatus: scannerViewModel.cameraAuthorizationStatus, onRequestPermission: { scannerViewModel.refreshCameraPermission() }, onOpenSettings: { scannerViewModel.openSettings() } ) } } .navigationTitle("扫描器") .navigationBarTitleDisplayMode(.inline) .navigationBarBackButtonHidden(false) .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 { Button(action: { logInfo("🔄 用户点击工具栏重新扫描按钮", className: "ScannerView") // 添加触觉反馈 let impactFeedback = UIImpactFeedbackGenerator(style: .medium) impactFeedback.impactOccurred() resetToScanning() }) { HStack(spacing: 6) { Image(systemName: "arrow.clockwise") .font(.system(size: 16, weight: .semibold)) Text("rescan_button".localized) .font(.system(size: 14, weight: .medium)) } .foregroundColor(.blue) } } } } .onAppear { // 只有在相机权限已授权时才启动扫描 if scannerViewModel.cameraAuthorizationStatus == .authorized { scannerViewModel.startScanning() } } .onDisappear { scannerViewModel.stopScanning() // 确保退出时关闭手电筒 if scannerViewModel.isTorchOn { scannerViewModel.turnOffTorch() } } .alert("scan_error_title".localized, isPresented: $scannerViewModel.showAlert) { Button("OK") { } } message: { Text("scan_error_message".localized) } .onReceive(scannerViewModel.$detectedCodes) { codes in handleDetectedCodes(codes) } .onReceive(scannerViewModel.$cameraAuthorizationStatus) { status in if status == .authorized { logInfo("🎯 相机权限已授权,启动扫描", className: "ScannerView") scannerViewModel.startScanning() } } .onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in handleOrientationChange() } } // MARK: - 私有方法 private func handleDetectedCodes(_ codes: [DetectedCode]) { guard !codes.isEmpty else { return } logInfo("检测到条码数量: \(codes.count)", className: "ScannerView") if codes.count == 1 { logInfo("单个条码,准备自动选择", className: "ScannerView") pauseForPreview() autoSelectSingleCode(code: codes[0]) } else { logInfo("多个条码,等待用户选择", className: "ScannerView") pauseForPreview() } } private func handleOrientationChange() { screenOrientation = UIDevice.current.orientation logInfo("Screen orientation changed to: \(screenOrientation.rawValue)", className: "ScannerView") } private func handleCodeSelection(_ selectedCode: DetectedCode) { logInfo("🎯 ScannerView 收到条码选择回调", className: "ScannerView") logInfo(" 选择的条码ID: \(selectedCode.id)", className: "ScannerView") logInfo(" 选择的条码类型: \(selectedCode.type)", className: "ScannerView") logInfo(" 选择的条码内容: \(selectedCode.content)", className: "ScannerView") logInfo(" 选择的条码位置: \(selectedCode.bounds)", className: "ScannerView") let formattedResult = "类型: \(selectedCode.type)\n内容: \(selectedCode.content)" logInfo(" 格式化结果: \(formattedResult)", className: "ScannerView") NotificationCenter.default.post(name: .scannerDidScanCode, object: formattedResult) // 使用导航返回,不需要手动dismiss } private func pauseForPreview() { showPreviewPause = true } private func resetToScanning() { logInfo("🔄 ScannerView 开始重置到扫描状态", className: "ScannerView") // 重置UI状态 showPreviewPause = false // 重置扫描状态并重新开始 scannerViewModel.resetDetection() scannerViewModel.restartScanning() // 延迟检查会话状态 DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { logInfo("🔍 检查扫描会话状态", className: "ScannerView") self.scannerViewModel.checkSessionStatus() } logInfo("✅ ScannerView 已重置到扫描状态", className: "ScannerView") } private func autoSelectSingleCode(code: DetectedCode) { logInfo("开始自动选择定时器,条码类型: \(code.type)", className: "ScannerView") DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { guard self.showPreviewPause && self.scannerViewModel.detectedCodes.count == 1 else { logInfo("条件不满足,取消自动选择", className: "ScannerView") return } logInfo("条件满足,执行自动选择", className: "ScannerView") self.handleCodeSelection(code) } } } #if DEBUG struct ScannerView_Previews: PreviewProvider { static var previews: some View { NavigationView { ScannerView() } } } #endif