这是一篇使用AVFoundation
框架识别二维码的文章,原文提供了初始化项目QRCodeReaderStarter,我重新建立项目,图片是从原文提供的初始化项目中copy的。
初始化项目
新建一个名为QRCodeReader的单页项目
-
删除ViewController.swift文件,在项目目录处右击新建文件,新建一个
QRCodeViewController
类继承自UIViewController
。
-
点击
Main.storyboard
,选中View Controller
,修改其属性Class
为QRCodeViewController
,使两者关联。
向
Main.storyboard
中拖动一个View Controller
对象,以1的方法新建一个继承自UIViewController
的类QRScannerController
,然后与Main.storyboard
中新建的View Controller
关联。下载图片。从原文的初始化项目QRCodeReaderStarter复制。
修改Code View Controller Scene的view背景颜色做修改;添加一个label,名称改为Quick Scan,字体,和字体大小也做修改;添加button,删除文本,大小为150150,
Type
为System,image
为focus*,Tint
为黄色;添加一些约束。Scanner Controller Scene: 在顶部添加一个View,在这个View中添加以label和button,button的image为cross;在顶部添加一个label;添加一些约束。
构建 segue
- 用control-drag从Code View Controller Scene中的button到Scanner Controller Scene,选择Present Modally
- 在
QRCodeViewController
中添加代码:
@IBAction func unwindToHomeScreen(segue: UIStoryboardSegue) {
//去除模态视图
dismiss(animated: true, completion: nil)
}
- 在Scanner Controller Scene 中,用control-drag从button到Exit,选择unwindToHomeScreenWithSegue:
- 6和7中样式和约束,合适即可。
加载 AVFoundation 框架
- 在QRScannerController.swift文件中引入AVFoundation Framework:
import AVFoundation
-
QRScannerController
实现AVCaptureMetadataOutputObjectsDelegate
协议:
class QRScannerController: UIViewController, AVCaptureMetadataOutputObjectsDelegate {
表示当捕获的元数据产生时,需要一些回调方法。 -
QRScannerController
中添加三个变量:
var captureSession: AVCaptureSession?
var videoPreviewLayer: AVCaptureVideoPreviewLayer?
var qrCodeFrameView: UIView?
实现视频捕获
- 在
QRScannerController
的viewDidLoad
中添加代码:
// 1
let captureDevice = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo)
do {
// 2
let input = try AVCaptureDeviceInput(device: captureDevice)
// 3
captureSession = AVCaptureSession()
// 4
captureSession?.addInput(input)
// 5
let captureMetadataOutput = AVCaptureMetadataOutput()
captureSession?.addOutput(captureMetadataOutput)
// 6
captureMetadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
captureMetadataOutput.metadataObjectTypes = [AVMetadataObjectTypeQRCode]
videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
videoPreviewLayer?.videoGravity = AVLayerVideoGravityResizeAspectFill
videoPreviewLayer?.frame = view.layer.bounds
view.layer.addSublayer(videoPreviewLayer!)
captureSession?.startRunning()
} catch {
print(error)
return
}
- 1 初始化
AVCaptureDevice
,它表示物理捕获设备;AVMediaTypeVideo
是Media types,表示视频类型,还有AVMediaTypeAudio
,AVMediaTypeText
等其他类型 - 2 根据
AVCaptureDevice
初始化捕获设备的输入数据对象AVCaptureDeviceInput
。这边的try是swift中错误处理的标识,对应public init(device: AVCaptureDevice!) throws
。 - 3
AVCaptureSession
用于协调输入与输出之间的数据流。 - 4 为
AVCaptureSession
添加输入数据 - 5 定义输出数据对象
AVCaptureMetaDataOutput
,并把它添加给AVCaptureSession
- 6 设置
self
为输出数据对象的代理,这就对应了QRScannerController
类实现AVCaptureMetadataOutputObjectsDelegate
协议。metadataObjectTypes
是识别的元数据类型,AVMetadataObjectTypeQRCode
表示二维码类型。
- 在
Info.plist
中添加key为NSCameraUsageDescription
或"Privacy – Camera Usage Description",值为 为了扫描二维码,需要使用你的相机。 - 运行应用时,点击scan button后,使用了相机,但messageLabel和tapbar没有了,因为被
AVCaptureVideoPreviewLayer
覆盖了,加入两行代码就可以显示了:
view.bringSubview(toFront: messageLabel)
view.bringSubview(toFront: topbar)
实现二维码读取
当检测到二维码时,用绿色框高亮;然后二维码被解码成文本信息展示在messagelabel中。
- 初始化绿色框。继续在上述代码后添加:
qrCodeFrameView = UIView()
if let qrCodeFrameView = qrCodeFrameView {
qrCodeFrameView.layer.borderColor = UIColor.green.cgColor
qrCodeFrameView.layer.borderWidth = 2
view.addSubview(qrCodeFrameView)
view.bringSubview(toFront: qrCodeFrameView)
}
由于qrCodeFrameView
没有设置大小,默认是0,所以不会显示,当之后发现二维码时再让它显示。
- 解码二维码
当AVCaptureMetadataOutput
识别出二维码时,AVCaptureMetadataOutputObjectsDelegate
中的代理方法将被调用:
optional public func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [Any]!, from connection: AVCaptureConnection!)
回调方法定义:
func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [Any]!, from connection: AVCaptureConnection!) {
if metadataObjects == nil || metadataObjects.count == 0 {
qrCodeFrameView?.frame = CGRect.zero
messageLabel.text = "没有二维码/条形码"
return
}
let metadataObj = metadataObjects[0] as! AVMetadataMachineReadableCodeObject
if metadataObj.type == AVMetadataObjectTypeQRCode {
// 1
let barCodeObject = videoPreviewLayer?.transformedMetadataObject(for: metadataObj)
qrCodeFrameView?.frame = barCodeObject!.bounds
if metadataObj.stringValue != nil {
// 2
messageLabel.text = metadataObj.stringValue
}
}
}
- 第二个参数
metadataObjects
是个数组,就是所有被识别出来的元数据对象。如果这个参数为nil或数组为空数组,就表示没有识别出对象。 - 1 通过
transformedMetadataObject
方法获得AVMetadataObject
对象,更加这个对象可以获得二维码的位置,然后显示绿框。 - 2 把二维码信息显示在messagelabel上。
识别条形码
除了识别二维码以外,还可以识别条形码。只要在captureMetadataOutput.metadataObjectTypes = [AVMetadataObjectTypeQRCode]
添加更多的元数据类型。
,然后回调方法中修改检测类型就可以。
let supportedCodeTypes = [AVMetadataObjectTypeUPCECode,
AVMetadataObjectTypeCode39Code,
AVMetadataObjectTypeCode39Mod43Code,
AVMetadataObjectTypeCode93Code,
AVMetadataObjectTypeCode128Code,
AVMetadataObjectTypeEAN8Code,
AVMetadataObjectTypeEAN13Code,
AVMetadataObjectTypeAztecCode,
AVMetadataObjectTypePDF417Code,
AVMetadataObjectTypeQRCode
]
captureMetadataOutput.metadataObjectTypes = supportedCodeTypes
// ....
if supportedCodeTypes.contains(metadataObj.type) {
let barCodeObject = videoPreviewLayer?.transformedMetadataObject(for: metadataObj)
qrCodeFrameView?.frame = barCodeObject!.bounds
if metadataObj.stringValue != nil {
messageLabel.text = metadataObj.stringValue
}
}
代码
QRCodeReader