import UIKit
import AVFoundation
/// 二維碼掃描控制器
class QRScanVC: UIViewController {
// MARK: - 私有屬性
/// 捕捉會(huì)話對(duì)象,管理輸入和輸出流
private var captureSession: AVCaptureSession!
/// 預(yù)覽圖層,用于顯示攝像頭畫面
private var previewLayer: AVCaptureVideoPreviewLayer!
/// 掃描線視圖
private var scanningLine: UIView!
/// 掃描線動(dòng)畫
private var animation: CABasicAnimation!
/// 掃描框的邊長(zhǎng)
private let squareSize: CGFloat = 300
/// 掃描框的 X 坐標(biāo)
private var squareX: CGFloat = 0
/// 掃描框的 Y 坐標(biāo)
private var squareY: CGFloat = 0
/// 掃描結(jié)果回調(diào)閉包
private var scanResultBlock: ((String) -> Void)?
// MARK: - 初始化
/// 初始化控制器并傳入掃描結(jié)果回調(diào)
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() // 初始化相機(jī)
addMaskToScannerView() // 添加遮罩和掃描框
setupCloseAndTitleView() // 設(shè)置頂部關(guān)閉按鈕和標(biāo)題
setupScanningLineAnimation() // 設(shè)置掃描線動(dòng)畫
setupPhotoLibraryButton() // 添加相冊(cè)導(dǎo)入按鈕
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
scanningLine.layer.add(animation, forKey: "scanning") // 添加掃描動(dòng)畫
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
scanningLine.layer.removeAllAnimations() // 移除動(dòng)畫
}
}
// MARK: - 相機(jī)相關(guān)設(shè)置
extension QRScanVC {
/// 初始化相機(jī)掃描功能
private func initializeCamera() {
captureSession = AVCaptureSession()
// 獲取默認(rèn)視頻設(shè)備(攝像頭)
guard let videoDevice = AVCaptureDevice.default(for: .video),
let videoInput = try? AVCaptureDeviceInput(device: videoDevice),
captureSession.canAddInput(videoInput) else { return }
// 添加視頻輸入流
captureSession.addInput(videoInput)
// 創(chuàng)建元數(shù)據(jù)輸出流并添加
let metadataOutput = AVCaptureMetadataOutput()
if captureSession.canAddOutput(metadataOutput) {
captureSession.addOutput(metadataOutput)
// 設(shè)置回調(diào)代理為 self,使用主線程回調(diào)
metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
metadataOutput.metadataObjectTypes = [.qr] // 只識(shí)別二維碼
} else {
return
}
// 創(chuàng)建預(yù)覽圖層,展示攝像頭畫面
previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
previewLayer.frame = view.bounds
previewLayer.videoGravity = .resizeAspectFill
view.layer.addSublayer(previewLayer)
// 異步啟動(dòng)攝像頭掃描
Asyncs.async { [weak self] in
self?.captureSession.startRunning()
}
}
/// 添加遮罩區(qū)域和中間的掃描框
private func addMaskToScannerView() {
squareX = (view.bounds.width - squareSize) / 2
squareY = (view.bounds.height - squareSize) / 2
// 創(chuàng)建遮罩視圖區(qū)域:上、下、左、右
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))
]
// 設(shè)置遮罩顏色并添加到視圖
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)
}
/// 設(shè)置頂部關(guān)閉按鈕和標(biāo)題
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()
}
}
/// 掃描線動(dòng)畫配置
private func setupScanningLineAnimation() {
animation = CABasicAnimation(keyPath: "position.y")
animation.fromValue = squareY
animation.toValue = squareY + squareSize
animation.duration = 2
animation.autoreverses = true // 自動(dòng)反向運(yùn)動(dòng)
animation.repeatCount = .infinity // 無限循環(huán)
}
}
// MARK: - 掃描識(shí)別回調(diào)
extension QRScanVC: AVCaptureMetadataOutputObjectsDelegate {
/// 當(dāng)掃描到二維碼結(jié)果后調(diào)用
func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
captureSession.stopRunning()
if let metadata = metadataObjects.first as? AVMetadataMachineReadableCodeObject,
let value = metadata.stringValue {
// 掃描成功后震動(dòng)提示
AudioServicesPlaySystemSound(SystemSoundID(kSystemSoundID_Vibrate))
scanResultBlock?(value) // 回調(diào)結(jié)果
}
dismiss(animated: true)
}
}
// MARK: - 相冊(cè)讀取二維碼
extension QRScanVC: TZImagePickerControllerDelegate {
/// 添加“相冊(cè)導(dǎo)入二維碼”按鈕
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~)
}
}
/// 從相冊(cè)選擇圖片后,嘗試識(shí)別二維碼
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: "識(shí)別二維碼內(nèi)容:\(message)")
scanResultBlock?(message)
}
dismiss(animated: true)
}
}
僅供參考
IMG_4059.PNG