权限配置
在Info.plist文件中配置相机权限 Privacy - Camera Usage Description
创建一个类,实现了AVCaptureMetadataOutputObjectsDelegate
协议,用于处理扫描到的元数据对象;并做权限处理
//
// ScannerViewModel.swift
//
// Created by 123 on 2023/10/25.
//
import Foundation
import AVFoundation
import SwiftUI
class 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 = .resizeAspectFill
guard 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) {_ in
self.scanPermission = 2
})
alertController.addAction(UIAlertAction(title: "设置", style: .default) { _ in
if 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 = 1
case .notDetermined: // 用户还没有决定
// 请求权限
AVCaptureDevice.requestAccess(for: .video) { granted in
if 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: ScannerViewModel
func 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.bounds
view.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:MeetingViewModel
var 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))
}
}
}
}
}