简单二维码扫描实现

最早熟识二维码还是因为微信扫一扫功能。其实二维码最早的设计初衷是用于追踪工业生产中的产品并替换信息存储能力有限的条形码。不过这些年智能手机的快速普及让二维码的应用场景得到了极大的拓展,所以作为一名开发者或迟或早都将要面对二维码识别问题。在很久以前的iOS开发中这个功能不得不依靠第三方库来实现,后来Apple在AVFoundation中极大的丰富了这类条码的识别能力。下面我将用很少的代码实现该功能,并且介绍AVFoundation中媒体捕捉的基本概念。

创建Demo

Demo的功能和UI都非常简单:我们通过摄像头对二维码进行扫描然后对获得信息进行解码并跳转到对应的URL网页,在扫描的过程中我们还会对其中的二维码外框进行高亮标记。其实二维码识别功能的实现非常的简单和直接,所有这些条码识别包括二维码其实都是基于AVFoundation框架的媒体捕捉。媒体捕捉的简单结构图如下,具体概念会在后面提到,当然还有一部分与输出信息处理的代理没有在图中列出来可以自己查看官方文档。

导入AVFoundation框架

首先我们新建一个视图控制器QRScannerController.swift,然后在文件的头部导入框架:

import AVFoundation

然后我们在QRScannerController.swift文件头部新建如下的变量:

var captureSession: AVCaptureSession?
var videoPreviewLayer: AVCaptureVideoPreviewLayer?
var qrCodeFrameView: UIView?

并对QRScannerController进行拓展实现AVCaptureMetadataOutputObjectsDelegate代理(用于二维码解码,详情看后面):

extension QRScannerController:AVCaptureMetadataOutputObjectsDelegate {
      
}

初始化媒体捕捉环境

正如前面提到的二维码的识别都是基于AVFoundation框架中的媒体捕捉功能,所以最重要当然是上面结构图中核心:AVCaptureSession所代表的捕捉环境的设置了。从结构图中我们能清晰的看见,AVCaptureSession首先需要就是输入设备,也就是一个AVCaptureDeviceInput的实例(可能为摄像头、麦克风),所以我们在viewDidLoad中加入以下代码:

let captureDevice = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo)
    
do {     
    //初始化媒体捕捉的输入流
    let input = try AVCaptureDeviceInput(device: captureDevice)
        
    //初始化captureSession
    captureSession = AVCaptureSession()
        
    //设置输入到Session
    captureSession?.addInput(input)
        
}  catch  {
    // 捕获到移除就退出
    print(error)
    return
}

因为我们的目标是实现二维码识别,所以这里调用了defaultDevice(withMediaType:)方法,并使用AVMediaTypeVideo为参数创建了视频设备的实例(在iOS中返回的是默认的后摄像头,而macOS中返回的是内置的FaceTime摄像头)。

为了实现实时捕捉,我们将上面设置好的输入设备实例添加到了AVCaptureSession实例中。AVCaptureSession作为AVFoundation捕捉功能的核心类,其实它的功能类似于一个虚拟的“插线板”,用来连接各种输入、输出的资源。AVCaptureSession会从物理设备中获得数据流并将这些数据输出到多个目的地,而开发人员可以按照要需求对这些线路进行动态配置。

接下来就需要设置输出对象了,在框架中二维码对应的数据输出类型为AVCaptureMetaDataOutput。该类对数据的处理都是通过代理AVCaptureMetadataOutputObjectsDelegate来实现的,这也是为什么上面我会对QRScannerController控制器进行拓展的原因。我们在viewDidLoaddo代码块中加入如下代码:

//设置输入流
let captureMetadataOutput = AVCaptureMetadataOutput()
captureSession?.addOutput(captureMetadataOutput)

//设置代理并指定输出为二维码
captureMetadataOutput.setMetadataObjectsDelegate(self,queue: DispatchQueue.main)
captureMetadataOutput.metadataObjectTypes = [AVMetadataObjectTypeQRCode]

上面的代码中我们将委托设置到默认的串行队列(Apple文档中要求为串行队列),等待委托对获取的元数据进行下一步处理。同时我们将metadataObjectTypes属性设为了AVMetadataObjectTypeQRCode也就是二维码,其他的类型有:

  • UPC-E (AVMetadataObjectTypeUPCECode)

  • 2Code 39 (AVMetadataObjectTypeCode39Code)

  • Code 39 mod 43 (AVMetadataObjectTypeCode39Mod43Code)

  • Code 93 (AVMetadataObjectTypeCode93Code)

  • Code 128 (AVMetadataObjectTypeCode128Code)

  • EAN-8 (AVMetadataObjectTypeEAN8Code)

  • EAN-13 (AVMetadataObjectTypeEAN13Code)

  • Aztec (AVMetadataObjectTypeAztecCode)

  • PDF417 (AVMetadataObjectTypePDF417Code)

设置好输入、输出后,我们还缺了个非常重要的功能,那就是实时预览抓捕的场景。幸运的是,框架中已经自带了AVCaptureVideoPreviewLayer类来满足该需求,该类是Core Animation中CALayer的一个SubClass。设置的代码如下:

//捕捉的实时预览图
videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
videoPreviewLayer?.videoGravity = AVLayerVideoGravityResizeAspectFill
videoPreviewLayer?.frame = view.layer.bounds
view.layer.addSublayer(videoPreviewLayer!)

万事具备后,我们开始捕捉:

// 开始捕获
captureSession?.startRunning()

如果你现在就在设备上运行Demo的话,对不起Apple会让你Crash

相关文章

软件简介:蓝湖辅助工具,减少移动端开发中控件属性的复制和粘...
现实生活中,我们听到的声音都是时间连续的,我们称为这种信...
前言最近在B站上看到一个漂亮的仙女姐姐跳舞视频,循环看了亿...
【Android App】实战项目之仿抖音的短视频分享App(附源码和...
前言这一篇博客应该是我花时间最多的一次了,从2022年1月底至...
因为我既对接过session、cookie,也对接过JWT,今年因为工作...