上篇讲了如何自定义一个UIView绘制出来一个方形的框。这篇分享如何使
AVFoundation来捕捉摄像头数据,并处理二维码解析出来的数据。
摄像头数据采集
摄像头采集很简单,只需要使用ios提供的API就能很容易的实现。在这个例子中,我们有一个初始化方法主要用来做摄像头数据采集的:
/** 初始化相机捕捉 **/ func initCapture(captureView:UIView, delegate:AVCaptureMetadataOutputObjectsDelegate) { let captureDevice = AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeVideo) var error: NSError? let input: AnyObject! = AVCaptureDeviceInput.deviceInputWithDevice(captureDevice, error: &error) if (error != nil) { println("\(error?.localizedDescription)") } else { let captureViewFrame = captureView.frame captureSession = AVCaptureSession() captureSession?.addInput(input as! AVCaptureInput) let captureMetadataOutput = AVCaptureMetadataOutput() let screenHeight = captureViewFrame.height; let screenWidth = captureViewFrame.width; let cropRect = self.frame; captureMetadataOutput.rectOfInterest = CGRectMake(cropRect.origin.y / screenHeight,cropRect.origin.x / screenWidth,cropRect.size.height / screenHeight,cropRect.size.width / screenWidth) captureSession?.addOutput(captureMetadataOutput) captureMetadataOutput.setMetadataObjectsDelegate(delegate, queue: dispatch_get_main_queue()) captureMetadataOutput.metadataObjectTypes = supportedBarCodes videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession) videoPreviewLayer?.videoGravity = AVLayerVideoGravityResizeAspectFill videoPreviewLayer?.frame = captureViewFrame captureView.layer.addSublayer(videoPreviewLayer) mMaskLayer = getMaskLayer(captureViewFrame) captureView.layer.addSublayer(mMaskLayer) captureView.bringSubviewToFront(self) moveAllToFront(captureView) } }
这里这些API不光是适用于二维码,包括条形码等等都可以处理,主要是通过这个属性来过滤:
captureMetadataOutput.metadataObjectTypes = supportedBarCodes
这个属性可以有很多值:
[AVMetadataObjectTypeQRCode, AVMetadataObjectTypeCode128Code, AVMetadataObjectTypeCode39Code, AVMetadataObjectTypeCode93Code, AVMetadataObjectTypeUPCECode, AVMetadataObjectTypePDF417Code, AVMetadataObjectTypeEAN13Code, AVMetadataObjectTypeAztecCode]
这里主要介绍二维码,所以我们只用一个。
这里还要对UIView的layer层级做一个说明。
我们使用了两个layer,一个是摄像头捕捉的layer:videoPreviewLayer
一个是蒙板layer这个layer的作用就是在中间扣一个白色的洞,让扫描框之外的区域看起来颜色更暗。初始化这个蒙板的代码如下:
/** 获取蒙板 **/ private func getMaskLayer(rect:CGRect) -> CAShapeLayer { let layer = CAShapeLayer.new() layer.fillColor = mBackgroundColor.CGColor layer.position = CGPoint(x: 0, y: 0) let fillPath = UIBezierPath(rect: rect) let scannerPath = UIBezierPath(rect: self.frame) fillPath.appendPath(scannerPath) layer.path = fillPath.CGPath layer.fillRule = kCAFillRuleEvenOdd return layer }
有了这两个蒙板我们只要按照顺序添加layer到UIView就好了。这里要注意的一个地方就是到目前为止,我们如果直接在布局文件中,放入控件,比如底上的那行字,这个时候运行,你是看不到这行字的,原因就是这行字的层级比蒙板层级要低,所以被挡住了,所以我们在添加完蒙板后,我们把父控件的每一个子控件移动到最顶层,当然移动的时候要排除我们这个二维码View:
private func moveAllToFront(view:UIView) { for var i = 0; i < view.subviews.count; ++i { if let view: QRScannerView = view.subviews[i] as? QRScannerView { } else { view.bringSubviewToFront(view.subviews[i] as! UIView) } } }
到此摄像头捕捉部分就完成了,下面介绍怎么添加横线移动的动画。
添加扫描线动画
添加动画代码很简单直接贴了:
func startLineRunning() { let rect = self.bounds let lineFrame = self.qrLine?.frame print(lineFrame) self.layer.removeAllAnimations() UIView.animateWithDuration(1.5, animations: { self.qrLine!.frame = CGRect(x: rect.origin.x, y: rect.height - lineFrame!.height, width: lineFrame!.width, height: lineFrame!.height) }){(Bool) in self.qrLine!.frame = CGRect(x: rect.origin.x, y: rect.origin.y, width: lineFrame!.width, height:lineFrame!.height) if self.mIsEnableLineAnimation { self.startLineRunning() } } }
完成动画后,我们需要在启动摄像头捕捉的时候让动画启动:
func startRunning() { captureSession?.startRunning() self.mIsEnableLineAnimation = true startLineRunning() } func stopRunning() { captureSession?.stopRunning() self.mIsEnableLineAnimation = false }
事例代码:
1、在controller实现协议:
AVCaptureMetadataOutputObjectsDelegate
2、其他代码包含启动、初始化、和摄像头捕捉的数据处理:
override func viewDidAppear(animated: Bool) { scanner.startRunning() isCaputure = false } override func viewDidDisappear(animated: Bool) { scanner.stopRunning() } override func viewWillAppear(animated: Bool) { scanner.initCapture(self.view, delegate: self) } var isCaputure = false /** 捕捉回调 **/ func captureOutput(captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [AnyObject]!, fromConnection connection: AVCaptureConnection!) { var resultString = "" if metadataObjects == nil || metadataObjects.count == 0 { resultString = "scanner error" } else { let metadataObj = metadataObjects[0] as! AVMetadataMachineReadableCodeObject if self.scanner.supportedBarCodes.filter({ $0 == metadataObj.type }).count > 0 { let barCodeObject = self.scanner.videoPreviewLayer?.transformedMetadataObjectForMetadataObject(metadataObj as AVMetadataMachineReadableCodeObject) as! AVMetadataMachineReadableCodeObject resultString = barCodeObject.stringValue } } print("isCaputure \(isCaputure)") if !isCaputure { isCaputure = true self.requestValidate(resultString) } }
完整demo下载地址:DemoQRView
更新(下载:QRScannerView类):
1、修复了横竖屏切换布局显示问题。
2、修复了动画重复启动,导致动画横线位置位移的问题。