二維碼/條形碼工作框圖:
輸入設備 <————> 會話 <————> 輸出設備
設置掃描區域
- 特別要注意的是輸出設備的屬性:rectOfInterest的坐標系是橫屏坐標系,因此要進行轉換
-
1. 懶加載
//1,輸入設備
private lazy var inputDevice:AVCaptureDeviceInput? = {
let device = AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeVideo)
return try! AVCaptureDeviceInput.init(device: device)
}()
//2,會話
private lazy var session:AVCaptureSession? = {
return AVCaptureSession.init()
}()
//3,輸出設備,設置輸出對象解析數據時聚焦的區域,默認值是全屏幕,也就是屏幕哪里掃到了二維碼就聚焦哪, CGRect(x: 0, y: 0, width: 1, height: 1),現在要求只聚焦制定框內的二維碼
private lazy var outputDevice:AVCaptureMetadataOutput? = {
let output = AVCaptureMetadataOutput()
// 通過對這個值的觀察, 我們發現傳入的是比例
// 注意: 參照是以橫屏的左上角作為, 而不是以豎屏
// 1.獲取屏幕的frame
let viewRect = self.view.frame
// 2.獲取掃描容器的frame
let containerRect = self.customContainerView.frame
let x = containerRect.origin.y / viewRect.height;
let y = containerRect.origin.x / viewRect.width;
let width = containerRect.height / viewRect.height;
let height = containerRect.width / viewRect.width;
output.rectOfInterest = CGRect(x: x, y: y, width: width, height: height)
return output
}()
//4,預覽掃描
private lazy var previewLayer:AVCaptureVideoPreviewLayer = {
return AVCaptureVideoPreviewLayer.init(session: self.session)
}()
//5,聚焦框
private lazy var focusLayer:CALayer = {
var va = CALayer.init()
return va
}()
2.開始掃描
//二維碼掃描
func QRScanning() -> Void {
//1,確認session是否可以添加輸入設備
if !(session?.canAddInput(inputDevice))!{
return
}
//2,確認session是否可以添加輸出設備
if !(session?.canAddOutput(outputDevice))! {
return
}
//3,添加輸入輸出設備
session?.addInput(inputDevice)
session?.addOutput(outputDevice)
//4,設置輸出設備能夠解析的數據
outputDevice?.metadataObjectTypes = outputDevice?.availableMetadataObjectTypes
//5,設置監聽輸出解析到的數據,調用代理方法
outputDevice?.setMetadataObjectsDelegate(self, queue:dispatch_get_main_queue())
//6,添加預覽圖層,使得預覽曾掃描框在一個限定范圍內
previewLayer.frame = view.frame
view.layer.insertSublayer(previewLayer, atIndex: 0)
//7,添加自定義的聚焦框
focusLayer.frame = view.bounds
view.layer.addSublayer(focusLayer)
//8,開始掃描
session?.startRunning()
}
3.實現代理方法
extension QRBarCodeViewController:AVCaptureMetadataOutputObjectsDelegate{
func captureOutput(captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [AnyObject]!, fromConnection connection: AVCaptureConnection!)
{
//metadataObjects元素類型是AVMetadataObject
guard let metadata: AVMetadataObject? = metadataObjects.last as? AVMetadataObject else{
return
}
//通過預覽圖層將數據,轉換為能識別的數據類型
let canReadData = previewLayer.transformedMetadataObjectForMetadataObject(metadata)
// DyLog(metadata) before:corners:{ 0.5,0.6 0.6,0.6 0.6,0.3 0.5,0.3 },
//DyLog(canReadData as! AVMetadataMachineReadableCodeObject)
//after convert:corners:{ 162.5,309.2 166.7,401.7 260.1,401.6 258.7,307.2 },
//清除之前的圖層
clearLayer()
guard let pointArrMetaData = canReadData as? AVMetadataMachineReadableCodeObject else{
return
}
//畫框
drawLine(pointArrMetaData)
}
func drawLine(data:AVMetadataMachineReadableCodeObject!) -> Void {
//校驗數據,查看數據的數組中是否含有corners,沒有直接返回,否則會數組越界
guard let array = data!.corners else{
return
}
//1,創建會話圖層,畫貝塞爾曲線的圖層CAShapeLayer
let drawLayer = CAShapeLayer.init()
//設置邊框寬度和顏色
drawLayer.fillColor = UIColor.clearColor().CGColor
drawLayer.strokeColor = UIColor.greenColor().CGColor
drawLayer.lineWidth = 3
//2,創建貝塞爾曲線
let bezierPath = UIBezierPath.init()
var index = 0;
//將數組中的字典轉為CGPoint
var start = CGPointZero
CGPointMakeWithDictionaryRepresentation((array[index] as! CFDictionary), &start)
bezierPath.moveToPoint(start)
//3,畫框
while index < array.count-1 {
index += 1
DyLog(index)
CGPointMakeWithDictionaryRepresentation((array[index] as! CFDictionary),&start)
bezierPath.addLineToPoint(start)
}
//4,關閉路徑
bezierPath.closePath()
//5,將貝塞爾曲線添加到圖層上
drawLayer.path = bezierPath.CGPath
//6,將圖層添加到聚焦界面上
focusLayer.addSublayer(drawLayer)
}
func clearLayer() -> Void {
guard let sublayers = focusLayer.sublayers else{
return
}
for layer in sublayers {
layer.removeFromSuperlayer()
}
}
}