You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

视图出现时重启相机: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()
}

修复方案

问题根源

  1. ScanView中viewInsideScan是静态常量,导致DataScannerView实例不会随视图生命周期重建,返回时无法正确重启扫描。
  2. updateUIViewController中未检查扫描状态,重复调用startScanning可能导致状态冲突。
  3. 视图消失后返回时,扫描器的重启逻辑不完整。

具体修改

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 00:18:12