权限配置
在Info.plist文件中配置相机权限 Privacy - Camera Usage Description
创建一个类,实现了AVCaptureMetadataOutputObjectsDelegate
协议,用于处理扫描到的元数据对象;并做权限处理
//
// ScannerViewModel.swift
//
// Created by 123 on 2023/10/25.
//import Foundation
import AVFoundation
import SwiftUIclass ScannerViewModel: NSObject,ObservableObject, AVCaptureMetadataOutputObjectsDelegate {var scanSession:AVCaptureSession!var previewLayer: AVCaptureVideoPreviewLayer?@Published var lastQrCode: String = ""var scanPermission = 0// 初始设置func prepareScan() {do {self.scanSession = AVCaptureSession()self.previewLayer = AVCaptureVideoPreviewLayer(session: self.scanSession)self.previewLayer?.videoGravity = .resizeAspectFillguard let videoCaptureDevice = AVCaptureDevice.default(for: .video)else { return }let videoInput = try AVCaptureDeviceInput(device: videoCaptureDevice)if (self.scanSession.canAddInput(videoInput)) {self.scanSession.addInput(videoInput)} else {return}let metadataOutput = AVCaptureMetadataOutput()if (self.scanSession.canAddOutput(metadataOutput)) {self.scanSession.addOutput(metadataOutput)metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)metadataOutput.metadataObjectTypes = [.qr]} else {print("Could not add Metadata output.")return}} catch {print("Failed to initialize scanner.")}}func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {self.scanSession.stopRunning()if let metadataObject = metadataObjects.first {guard let readableObject = metadataObject as? AVMetadataMachineReadableCodeObject else { return }guard let stringValue = readableObject.stringValue else { return }lastQrCode = stringValue// 这里可以获取扫描到的二维码内容print("QR Code Scanned: \(stringValue)")}}func showPermissionDeniedAlert() {guard let rootViewController = UIApplication.shared.windows.filter({$0.isKeyWindow}).first?.rootViewController else {return}let alertController = UIAlertController(title: "相机权限",message: "二维码扫描需要访问相机。请在设置中启用相机权限。",preferredStyle: .alert)alertController.addAction(UIAlertAction(title: "取消", style: .default) {_ inself.scanPermission = 2})alertController.addAction(UIAlertAction(title: "设置", style: .default) { _ inif let settingsUrl = URL(string: UIApplication.openSettingsURLString) {if UIApplication.shared.canOpenURL(settingsUrl) {UIApplication.shared.open(settingsUrl)}}})rootViewController.present(alertController, animated: true, completion: nil)}// 函数来检查权限并进行适当处理func checkPermissions() {switch AVCaptureDevice.authorizationStatus(for: .video) {case .authorized: // 用户已经授权self.prepareScan() // 继续设置扫描self.scanPermission = 1case .notDetermined: // 用户还没有决定// 请求权限AVCaptureDevice.requestAccess(for: .video) { granted inif granted {DispatchQueue.main.async {self.prepareScan() // 如果被授权,再次调用准备扫描的方法self.scanPermission = 1}}else{// 如果不允许,则可以在这里处理,如弹出界面提示用户self.scanPermission = 2}}case .denied, .restricted: // 用户被禁用或在家长控制下限制if(scanPermission != 2){// 弹出窗口告诉用户他们没有权限showPermissionDeniedAlert()}print("Camera permissions are either denied or restricted.")@unknown default:fatalError("Unknown status of camera authorization.")}}
}
2.实现UIViewRepresentable,展示扫描视频流
//
// QRScannerView.swift
//实现 UIViewRepresentable 协议,这是一个用于在 SwiftUI 中嵌入 UIKit 视图的抽象。结构体中包含一个 @ObservedObject 修饰的 ScannerViewModel 实例,使得视图可以对 ViewModel 的更改做出响应。
// Created by 123 on 2023/10/25.
//import Foundation
import SwiftUI
import AVFoundation
struct QRScannerView: UIViewRepresentable {@ObservedObject var scannerViewModel: ScannerViewModelfunc makeUIView(context: Context) -> UIView {let view = UIView(frame: CGRect.zero)DispatchQueue.main.async {// 调用 scannerViewModel.checkPermissions() 检查相机权限。self.scannerViewModel.checkPermissions()// 设置 preview layer 的 frame 为新 UIView 实例的边界,并将其添加到 UIView 的 layer 上。显示来自摄像头的视频流if let previewLayer = self.scannerViewModel.previewLayer {previewLayer.frame = view.layer.boundsview.layer.addSublayer(previewLayer)}if(self.scannerViewModel.scanPermission==1){self.scannerViewModel.scanSession.startRunning()}}return view}func updateUIView(_ view: UIView, context: Context) {if let previewLayer = scannerViewModel.previewLayer {previewLayer.frame = view.layer.bounds}}}
3.扫码页面
struct ScannerView:View{@StateObject var scannerViewModel = ScannerViewModel()let meetingViewModel:MeetingViewModelvar body: some View {ZStack {QRScannerView(scannerViewModel: scannerViewModel)if scannerViewModel.lastQrCode != "" {Text("Last scanned QR Code: \(scannerViewModel.lastQrCode)").foregroundColor(.white).padding().background(Color.black.opacity(0.7))}}}}
}