import UIKit
import AVFoundation
/// 二维码扫描控制器
class QRScanVC: UIViewController {
// MARK: - 私有属性
/// 捕捉会话对象,管理输入和输出流
private var captureSession: AVCaptureSession!
/// 预览图层,用于显示摄像头画面
private var previewLayer: AVCaptureVideoPreviewLayer!
/// 扫描线视图
private var scanningLine: UIView!
/// 扫描线动画
private var animation: CABasicAnimation!
/// 扫描框的边长
private let squareSize: CGFloat = 300
/// 扫描框的 X 坐标
private var squareX: CGFloat = 0
/// 扫描框的 Y 坐标
private var squareY: CGFloat = 0
/// 扫描结果回调闭包
private var scanResultBlock: ((String) -> Void)?
// MARK: - 初始化
/// 初始化控制器并传入扫描结果回调
convenience init(_ result: ((_ text: String) -> Void)?) {
self.init(nibName: nil, bundle: nil)
self.modalPresentationStyle = .fullScreen
self.scanResultBlock = result
}
// MARK: - 生命周期
override func viewDidLoad() {
super.viewDidLoad()
initializeCamera() // 初始化相机
addMaskToScannerView() // 添加遮罩和扫描框
setupCloseAndTitleView() // 设置顶部关闭按钮和标题
setupScanningLineAnimation() // 设置扫描线动画
setupPhotoLibraryButton() // 添加相册导入按钮
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
scanningLine.layer.add(animation, forKey: "scanning") // 添加扫描动画
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
scanningLine.layer.removeAllAnimations() // 移除动画
}
}
// MARK: - 相机相关设置
extension QRScanVC {
/// 初始化相机扫描功能
private func initializeCamera() {
captureSession = AVCaptureSession()
// 获取默认视频设备(摄像头)
guard let videoDevice = AVCaptureDevice.default(for: .video),
let videoInput = try? AVCaptureDeviceInput(device: videoDevice),
captureSession.canAddInput(videoInput) else { return }
// 添加视频输入流
captureSession.addInput(videoInput)
// 创建元数据输出流并添加
let metadataOutput = AVCaptureMetadataOutput()
if captureSession.canAddOutput(metadataOutput) {
captureSession.addOutput(metadataOutput)
// 设置回调代理为 self,使用主线程回调
metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
metadataOutput.metadataObjectTypes = [.qr] // 只识别二维码
} else {
return
}
// 创建预览图层,展示摄像头画面
previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
previewLayer.frame = view.bounds
previewLayer.videoGravity = .resizeAspectFill
view.layer.addSublayer(previewLayer)
// 异步启动摄像头扫描
Asyncs.async { [weak self] in
self?.captureSession.startRunning()
}
}
/// 添加遮罩区域和中间的扫描框
private func addMaskToScannerView() {
squareX = (view.bounds.width - squareSize) / 2
squareY = (view.bounds.height - squareSize) / 2
// 创建遮罩视图区域:上、下、左、右
let masks = [
UIView(frame: CGRect(x: 0, y: 0, width: view.bounds.width, height: squareY)),
UIView(frame: CGRect(x: 0, y: squareY + squareSize, width: view.bounds.width, height: view.bounds.height - (squareY + squareSize))),
UIView(frame: CGRect(x: 0, y: squareY, width: squareX, height: squareSize)),
UIView(frame: CGRect(x: squareX + squareSize, y: squareY, width: view.bounds.width - (squareX + squareSize), height: squareSize))
]
// 设置遮罩颜色并添加到视图
masks.forEach {
$0.backgroundColor = UIColor.black.withAlphaComponent(0.9)
view.addSubview($0)
}
// 中间绿色边框扫描框
let squareFrame = UIView(frame: CGRect(x: squareX, y: squareY, width: squareSize, height: squareSize))
squareFrame.layer.borderColor = UIColor.green.cgColor
squareFrame.layer.borderWidth = 3
squareFrame.backgroundColor = .clear
view.addSubview(squareFrame)
// 添加红色扫描线
scanningLine = UIView(frame: CGRect(x: squareX, y: squareY, width: squareSize, height: 2))
scanningLine.backgroundColor = UIColor.red
view.addSubview(scanningLine)
}
/// 设置顶部关闭按钮和标题
private func setupCloseAndTitleView() {
let closeButton = UIButton(image: "icon_close_w")
closeButton.addActionWithBlock { [weak self] _ in
self?.captureSession.stopRunning()
self?.dismiss(animated: true)
}
view.addSubview(closeButton)
closeButton.snp.makeConstraints { make in
make.top.equalToSuperview().inset(ScreenStatusBarHeight + 2~)
make.leading.equalToSuperview().inset(20~)
make.size.equalTo(40~)
}
let titleLabel = UILabel(text: "Scan", size: 18, color: .cWhite, weight: .bold)
view.addSubview(titleLabel)
titleLabel.snp.makeConstraints { make in
make.centerY.equalTo(closeButton)
make.centerX.equalToSuperview()
}
}
/// 扫描线动画配置
private func setupScanningLineAnimation() {
animation = CABasicAnimation(keyPath: "position.y")
animation.fromValue = squareY
animation.toValue = squareY + squareSize
animation.duration = 2
animation.autoreverses = true // 自动反向运动
animation.repeatCount = .infinity // 无限循环
}
}
// MARK: - 扫描识别回调
extension QRScanVC: AVCaptureMetadataOutputObjectsDelegate {
/// 当扫描到二维码结果后调用
func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
captureSession.stopRunning()
if let metadata = metadataObjects.first as? AVMetadataMachineReadableCodeObject,
let value = metadata.stringValue {
// 扫描成功后震动提示
AudioServicesPlaySystemSound(SystemSoundID(kSystemSoundID_Vibrate))
scanResultBlock?(value) // 回调结果
}
dismiss(animated: true)
}
}
// MARK: - 相册读取二维码
extension QRScanVC: TZImagePickerControllerDelegate {
/// 添加“相册导入二维码”按钮
private func setupPhotoLibraryButton() {
let photoButton = UIButton(image: "QRAlbum")
photoButton.addActionWithBlock { [weak self] _ in
guard let imagePicker = TZImagePickerController(maxImagesCount: 1, delegate: self) else { return }
imagePicker.allowCrop = false
imagePicker.allowTakeVideo = false
imagePicker.allowPickingVideo = false
imagePicker.preferredLanguage = LM.shared.currentLanguage.localeIdentifier
imagePicker.modalPresentationStyle = .fullScreen
self?.present(imagePicker, animated: true)
}
photoButton.cornerRadius(20~)
view.addSubview(photoButton)
photoButton.snp.makeConstraints { make in
make.bottom.equalToSuperview().inset(ScreenBottomSafeAreaHeight + 20~)
make.centerX.equalToSuperview()
make.size.equalTo(40~)
}
}
/// 从相册选择图片后,尝试识别二维码
func imagePickerController(_ picker: TZImagePickerController!, didFinishPickingPhotos photos: [UIImage]!, sourceAssets: [Any]!, isSelectOriginalPhoto: Bool) {
guard let image = photos.first else { return }
captureSession.stopRunning()
let ciImage = CIImage(image: image)!
let context = CIContext()
let detector = CIDetector(ofType: CIDetectorTypeQRCode, context: context, options: [CIDetectorAccuracy: CIDetectorAccuracyHigh])
if let features = detector?.features(in: ciImage),
let qrFeature = (features as? [CIQRCodeFeature])?.first,
let message = qrFeature.messageString {
PrintLog(message: "识别二维码内容:\(message)")
scanResultBlock?(message)
}
dismiss(animated: true)
}
}
仅供参考
IMG_4059.PNG