什么是QR Code?
QR (short for Quick Response),是二维条形码。最初设计用于制造业的跟踪部分。QR Code最近几年变的流行起来并作为编码URL加载网页信息的一种方式,不像基本的条形码(basic barcode ),QR Code包含了水平和垂直方向的信息,因此可以存储更多的数据,对于更多的认识可以看内容 official website of QR code.QR Code如下图所示:
基本类的介绍
AVCaptureSession
为了执行实时的捕获,我们需要初始化AVCaptureSession对象并添加输入设备。AVCaptureSession对象用于调节输入设备到输出设备的video数据流动。对于更详情的信息,请看这里。
AVCaptureDevice
AVCaptureDevice对象代表真实的物理拍摄设备(physical capture device),能够使用该设备对象配置潜在的硬件属性。但是要执行真正的捕捉操作,我们必须初始化AVCaptureSession对象并且添加输入设备。 是关于相机硬件的接口。它被用于控制硬件特性,诸如镜头的位置、曝光、闪光灯等。对于更详情的信息,请看这里。
AVCaptureDeviceInput
AVCaptureDeviceInput是
AVCaptureInput的子类,可以使用该类从
AVCaptureDevice对象获取数据。CaptureInput是一个抽象的基类,描述了输入数据到AVCaptureSession对象。为了将
CaptureInput对象与session相关联,需要调用session的
addInput(_:)
方法。对于更多详情信息,请看这里。
AVCaptureOutput
AVCaptureOutput是一个抽象类,描述 capture session 的结果。以下是三种关于静态图片捕捉的具体子类:
AVCaptureStillImageOutput 用于捕捉静态图片
AVCaptureMetadataOutput 启用检测人脸和二维码,AVCaptureMetadataOutput对象拦截元数据对象发出的相关捕获连接,并将它们转发给委托对象进行处理。您可以使用这个类的实例来处理特定类型的元数据中包含的输入数据。你使用这个类做其他对象输出的方式,通常是添加它作为AVCaptureSession对象的输出。对于更多详情信息,请看这里。
AVCaptureVideoOutput 为实时预览图提供原始帧
AVCaptureVideoPreviewLayer
AVCaptureVideoPreviewLayer是
CALayer
的子类,能够用于显示输入设备捕获的video。可以使用该预览层与AVCaptureSession相结合,如下代码所示:
AVCaptureSession *captureSession = <#Get a capture session#>;
AVCaptureVideoPreviewLayer *previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:captureSession];
UIView *aView = <#The view in which to present the layer#>;
previewLayer.frame = aView.bounds; // Assume you want the preview layer to fill the view.
[aView.layer addSublayer:previewLayer];
还可以使用
videoGravity属性来设置内容视图与layer大小之间的关系,就是相当于设置UIView的
contentMode属性。
功能实现效果和描述
由上图可知整个工程非常简单,运行程序即可看到Quick Scan界面,点击扫描按钮调到扫描界面,这里首先会进行用户授权,然后进行扫码。扫描时会为二维码添加绿色矩形框来高亮二维码部分,并且扫描成功之后,会从底部弹出提示框,选择是否打开扫描的结果。因为我们扫描二维码得到了一个URL,所以可以进行跳转网页。
对于用户授权我们需要在Info.plist文件中添加NSCameraUsageDescription作为key值并给与使用的原因。 如果没有设置该key值那么运行在iOS10上,会出现崩溃,并出现如下错误信息:This app has crashed because it attempted to access privacy-sensitive data without a usage description. The app's Info.plist must contain an NSCameraUsageDescription key with a string value explaining to the user how the app uses this data. 因为iOS要求APP开发者在访问照相机之前需要获取用户的权限。具体设置效果图如下:
然后对于扫描部分,具体效果如下GIF图
具体代码分析,例子原文地址 主要分析扫描视图控制页面功能实现
这部分代码非常简单,每一步都添加了相应的注释,主要是实现了实现设置设备捕获的媒体类型,然后创建对应的输入设备并添加到会话对象,然后创建对应的输出设备加入到会话对象中,紧接着创建预览显示层进行显示捕获的内容,最后创建一个view高亮扫描的二维码部分。
class QRScannerController: UIViewController,AVCaptureMetadataOutputObjectsDelegate {
//messageLabel用于显示编码信息
@IBOutlet var messageLabel:UILabel!
@IBOutlet var topbar: UIView!
var captureSession:AVCaptureSession?
var videoPreviewLayer:AVCaptureVideoPreviewLayer?
var qrCodeFrameView:UIView?
override func viewDidLoad() {
super.viewDidLoad()
//使用AVMediaTypeVideo作为媒体类型获取AVCaptureDevice类实例.
let captureDevice = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo)
do{
//使用前面创建的设备实例获取AVCaptureDeviceInput类实例。
let input = try AVCaptureDeviceInput(device: captureDevice)
//初始化AVCaptureSession类实例对象。
//为了执行实时的捕获,我们需要初始化AVCaptureSession对象并添加输入设备。AVCaptureSession对象用于调节video输入设备到输出设备的数据流动
captureSession = AVCaptureSession()
//添加输入设备到会话对象,设置AVCaptureSession的输入设备
captureSession?.addInput(input)
//AVCaptureMetaDataOutput类配合AVCaptureMetadataOutputObjectsDelegate协议,用于截获任意输入设备的元数据(metadata)并转换该数据为人类可读格式。
//初始化AVCaptureMetadataOutput对象并添加到会话对象作为输出设备
let captureMetadataOutput = AVCaptureMetadataOutput()
captureSession?.addOutput(captureMetadataOutput)
//设置代理并使用默认的主列队,必须使用串行列队,所以我们使用默认主串行列队
captureMetadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
//设置我们需要的元数据对象类型,相当于过来其他类型对象数据。
captureMetadataOutput.metadataObjectTypes = [AVMetadataObjectTypeQRCode]
//再配置了输出AVCaptureMetadataOutput对象之后,我们需要使用AVCaptureVideoPreviewLayer来显示捕获的video。它实际上是一个layer,我们能够使用这个预览层与AV capture session进行连接显示video。预览层被添加到当前视图作为子layer。
videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
videoPreviewLayer?.videoGravity = AVLayerVideoGravityResizeAspectFill
videoPreviewLayer?.frame = view.layer.bounds
view.layer.addSublayer(videoPreviewLayer!)
//最后通过调用startRunning方法开始执行捕获video
captureSession?.startRunning()
view.bringSubview(toFront: messageLabel)
view.bringSubview(toFront: topbar)
//创建一个view用于高亮QR code,首先是不可见的,因为大小并没有设置为0.当检测时,我们将改变大小显示
qrCodeFrameView = UIView()
if let qrCodeFrameView = qrCodeFrameView{
qrCodeFrameView.layer.borderColor = UIColor.green.cgColor
qrCodeFrameView.layer.borderWidth = 2
view.addSubview(qrCodeFrameView)
view.bringSubview(toFront: qrCodeFrameView)
}
}catch{
print(error)
return
}
}
//这里添加后续代码,为了方便查看分几个部分显示代码。。。。。。
}
这里是实现AVCaptureMetadataOutputObjectsDelegate协议中的optionalpublicfunc captureOutput(_ captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [Any]!, from connection: AVCaptureConnection!)方法,在该方法中处理扫描的结果内容。
//当AVCaptureMetadataOutput对象识别QR code将触发该方法
func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [Any]!, from connection: AVCaptureConnection!) {
//确认是否元数据对象数组是否不为nil并至少包含一个对象,如果为nil,那么显示对应的提示信息
if metadataObjects == nil || metadataObjects.count == 0{
qrCodeFrameView?.frame = CGRect.zero
messageLabel.text = "No QR code is detected"
return
}
//获取元数据对象,metadataObjects数组包含了所有读取的元数据
let metadataObj = metadataObjects[0] as! AVMetadataMachineReadableCodeObject
if metadataObj.type == AVMetadataObjectTypeQRCode{
//如果发现元数据是与QR code元数据相等,这时更新label内容并设置高亮视图的大小,执行transformedMetadataObject方法转换坐标
let barCodeObject = videoPreviewLayer?.transformedMetadataObject(for: metadataObj)
qrCodeFrameView?.frame = barCodeObject!.bounds
if metadataObj.stringValue != nil{
messageLabel.text = metadataObj.stringValue
//确保只弹出一次提示框,因为每次扫描的时候,方法都会调用,并且警告框已经弹出,不能够多次弹出警告框,造成不断生成对象而不释放
if presentedViewController != nil{
return
}
launchApp(metadataObj.stringValue)
}
}
}
最后launchApp函数仅仅是实现了对扫描结果进行选择是否打开扫描的URL
//实现打开扫描的URL,弹出actionSheet来选择是否打开
func launchApp(_ decodedURL: String){
let alertPromet = UIAlertController(title: "Open App", message: "You're going to open \(decodedURL)", preferredStyle: .actionSheet)
let confirmAction = UIAlertAction(title: "confirm", style: .default) { (action) in
if let url = URL(string: decodedURL){
if UIApplication.shared.canOpenURL(url){
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
}
}
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
alertPromet.addAction(confirmAction)
alertPromet.addAction(cancelAction)
present(alertPromet, animated: true, completion: nil)
}
参考: