视图出现时重启相机:iOS条码扫码器切换视图后失效问题
iOS扫码视图返回后相机停止工作的修复方案
问题说明
开发iOS条码扫码功能时,首次进入扫码视图相机正常工作,但切换到其他视图再返回后,相机仅短暂运行就停止,需要实现每次返回扫码视图时相机能持续正常工作。
相关代码
1. ScannerDataViewModel
import AVKit import Foundation import SwiftUI import VisionKit enum ScanType: String { case barcode, text } enum DataScannerAccessStatusType { case notDetermined case cameraAccessNotGranted case cameraNotAvailable case scannerAvailable case scannerNotAvailable } @MainActor final class ScannerDataViewModel: ObservableObject { @Published var dataScannerAccessStatus: DataScannerAccessStatusType = .notDetermined @Published var recognizedItems: [RecognizedItem] = [] @Published var scanType: ScanType = .barcode @Published var textContentType: DataScannerViewController.TextContentType? @Published var recognizesMultipleItems = true var recognizedDataType: DataScannerViewController.RecognizedDataType { scanType == .barcode ? .barcode() : .text(textContentType: textContentType) } var headerText: String { if recognizedItems.isEmpty { return "Scansione \(scanType.rawValue)" } else { return "Riconosciuti \(recognizedItems.count) oggetti" } } var dataScannerViewId: Int { var hasher = Hasher() hasher.combine(scanType) hasher.combine(recognizesMultipleItems) if let textContentType { hasher.combine(textContentType) } return hasher.finalize() } private var isScannerAvailable: Bool { DataScannerViewController.isAvailable && DataScannerViewController.isSupported } func requestDataScannerAccessStatus() async { guard UIImagePickerController.isSourceTypeAvailable(.camera) else { dataScannerAccessStatus = .cameraNotAvailable return } switch AVCaptureDevice.authorizationStatus(for: .video) { case .authorized: dataScannerAccessStatus = isScannerAvailable ? .scannerAvailable : .scannerNotAvailable case .restricted, .denied: dataScannerAccessStatus = .cameraAccessNotGranted case .notDetermined: let granted = await AVCaptureDevice.requestAccess(for: .video) if granted { dataScannerAccessStatus = isScannerAvailable ? .scannerAvailable : .scannerNotAvailable } else { dataScannerAccessStatus = .cameraAccessNotGranted } default: break } } }
2. DataScannerView
import Foundation import SwiftUI import VisionKit struct DataScannerView: UIViewControllerRepresentable { @Binding var recognizedItems: [RecognizedItem] let recognizedDataType: DataScannerViewController.RecognizedDataType let recognizesMultipleItems: Bool func makeUIViewController(context: Context) -> DataScannerViewController { let vc = DataScannerViewController( recognizedDataTypes: [recognizedDataType], qualityLevel: .balanced, recognizesMultipleItems: recognizesMultipleItems, isGuidanceEnabled: true, isHighlightingEnabled: true ) return vc } func updateUIViewController(_ uiViewController: DataScannerViewController, context: Context) { uiViewController.delegate = context.coordinator try? uiViewController.startScanning() } func makeCoordinator() -> Coordinator { Coordinator(recognizedItems: $recognizedItems) } static func dismantleUIViewController(_ uiViewController: DataScannerViewController, coordinator: Coordinator) { uiViewController.stopScanning() } class Coordinator: NSObject, DataScannerViewControllerDelegate { @Binding var recognizedItems: [RecognizedItem] init(recognizedItems: Binding<[RecognizedItem]>) { self._recognizedItems = recognizedItems } func dataScanner(_ dataScanner: DataScannerViewController, didTapOn item: RecognizedItem) { print("didTapOn \(item)") } func dataScanner(_ dataScanner: DataScannerViewController, didAdd addedItems: [RecognizedItem], allItems: [RecognizedItem]) { UINotificationFeedbackGenerator().notificationOccurred(.success) recognizedItems.append(contentsOf: addedItems) print("didAddItems \(addedItems)") } func dataScanner(_ dataScanner: DataScannerViewController, didRemove removedItems: [RecognizedItem], allItems: [RecognizedItem]) { self.recognizedItems = recognizedItems.filter { item in !removedItems.contains(where: {$0.id == item.id }) } print("didRemovedItems \(removedItems)") } func dataScanner(_ dataScanner: DataScannerViewController, becameUnavailableWithError error: DataScannerViewController.ScanningUnavailable) { print("became unavailable with error \(error.localizedDescription)") } } }
3. ScanView
import SwiftUI struct ScanView: View { @Environment(\.presentationMode) var presentationMode @StateObject private var vm: ScannerDataViewModel = ScannerDataViewModel() private let viewInsideScan = ViewInsideScanView() var body: some View { NavigationView { ZStack { Color("whiteBackground").ignoresSafeArea() VStack(spacing: 8) { Text("Posiziona il codice a barre al centro") .font(.title3) .foregroundColor(Color.primary) .padding(.top, 20) Text("La scansione inizierà automaticamente") .font(.callout) .foregroundColor(Color.primary) Spacer() CenteredSquareView(content: viewInsideScan) .frame(width: 350, height: 350, alignment: .center) Spacer() Image(systemName: "qrcode.viewfinder") .font(.largeTitle) .foregroundColor(.gray) Text("Tocca l'icona per eseguire una nuova scansione") .font(.callout) .foregroundColor(Color.primary) Spacer(minLength: 45) } } .environmentObject(vm) .onAppear { Task { await vm.requestDataScannerAccessStatus() } } } } } struct CenteredSquareView<Content: View>: View { var content: Content var body: some View { GeometryReader { geometry in ZStack { content .frame(width: geometry.size.width * 1, height: geometry.size.width * 1) .cornerRadius(20) .position(x: geometry.size.width / 2, y: geometry.size.height / 2) // Posiziona al centro } } .frame(maxWidth: .infinity, maxHeight: .infinity) } } #Preview { ScanView() }
修复方案
问题根源
ScanView中viewInsideScan是静态常量,导致DataScannerView实例不会随视图生命周期重建,返回时无法正确重启扫描。updateUIViewController中未检查扫描状态,重复调用startScanning可能导致状态冲突。- 视图消失后返回时,扫描器的重启逻辑不完整。
具体修改
1. 修复ScanView的视图实例创建
将private let viewInsideScan = ViewInsideScanView()替换为直接在CenteredSquareView中创建实例,确保每次返回视图时视图能重新初始化:
// ScanView的body中修改CenteredSquareView的调用 CenteredSquareView(content: ViewInsideScanView()) .frame(width: 350, height: 350, alignment: .center)
2. 优化DataScannerView的扫描状态处理
在updateUIViewController中添加扫描状态检查,避免重复启动:
func updateUIViewController(_ uiViewController: DataScannerViewController, context: Context) { // 仅在代理未绑定的情况下设置代理 if uiViewController.delegate !== context.coordinator { uiViewController.delegate = context.coordinator } // 仅在未扫描时启动 if !uiViewController.isScanning { try? uiViewController.startScanning() } }
3. 增强视图生命周期的扫描控制(可选)
给DataScannerView添加isActive绑定,通过视图的onAppear/onDisappear精确控制扫描状态:
首先修改DataScannerView:
struct DataScannerView: UIViewControllerRepresentable { @Binding var recognizedItems: [RecognizedItem] let recognizedDataType: DataScannerViewController.RecognizedDataType let recognizesMultipleItems: Bool @Binding var isActive: Bool // 新增绑定 func makeUIViewController(context: Context) -> DataScannerViewController { let vc = DataScannerViewController( recognizedDataTypes: [recognizedDataType], qualityLevel: .balanced, recognizesMultipleItems: recognizesMultipleItems, isGuidanceEnabled: true, isHighlightingEnabled: true ) return vc } func updateUIViewController(_ uiViewController: DataScannerViewController, context: Context) { uiViewController.delegate = context.coordinator // 根据isActive状态启停扫描 if isActive && !uiViewController.isScanning { try? uiViewController.startScanning() } else if !isActive && uiViewController.isScanning { try? uiViewController.stopScanning() } } // 其他代码保持不变 }
然后修改ScanView:
struct ScanView: View { @Environment(\.presentationMode) var presentationMode @StateObject private var vm: ScannerDataViewModel = ScannerDataViewModel() @State private var isScannerActive = false // 新增状态 var body: some View { NavigationView { ZStack { // ... 其他代码 ... CenteredSquareView(content: DataScannerView( recognizedItems: $vm.recognizedItems, recognizedDataType: vm.recognizedDataType, recognizesMultipleItems: vm.recognizesMultipleItems, isActive: $isScannerActive // 传入绑定 )) // ... 其他代码 ... } .environmentObject(vm) .onAppear { Task { await vm.requestDataScannerAccessStatus() isScannerActive = true // 视图出现时激活扫描 } } .onDisappear { isScannerActive = false // 视图消失时停止扫描 } } } }
4. 移除无用变量
删除ScannerDataViewModel中未使用的@State private var session: AVCaptureSession = .init(),因为DataScannerViewController内部已管理会话。
内容的提问来源于stack exchange,提问作者GiuAte
相关产品推荐
相关产品推荐

