上篇讲了如何自定义一个UIView绘制出来一个方形的框。这篇分享如何使

AVFoundation来捕捉摄像头数据,并处理二维码解析出来的数据。qr_scanner_ui

摄像头数据采集

摄像头采集很简单,只需要使用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下载地址:DemoQRViewContinue reading

二维码识别是很常见的app功能,为了更方便的在每一个使用二维码功能地方都能更快的实现,我把二维码功能写入到了一个自动的View里面,使用的时候和普通的UIView是一样的。效果如图(因为是模拟器运行的,所以摄像头看不到,用真机的时候就正常了):

ios_qr

 

分成3步来实现:

1、自定义UIView,实现方形的块。
2、实现摄像头捕捉。
3、扫描的横线动画。

自定义UIView

首先新建一个类继承自UIView

class QRScannerView: UIView

接着实现两个重要的方法:

required init(coder aDecoder: NSCoder)// 这个方法实现的目的是,我们在storyboard文件中使用这个View的时候,会直接显示出来效果。
override func drawRect(rect: CGRect)// 这个实现的目的是绘制我们要显示的内容

Ios_qr_ui
两个方法实现的代码如下:

    required init(coder aDecoder: NSCoder)
    {
        super.init(coder: aDecoder)
        self.initView()
    }
    override func drawRect(rect: CGRect)
    {
        let centerRect = getScannerRect(rect)
        //获取画图上下文
        let context:CGContextRef = UIGraphicsGetCurrentContext();
        CGContextSetAllowsAntialiasing(context, true)
        
        // 填充整个控件区域
        CGContextSetFillColorWithColor(context, mBackgroundColor.CGColor)
        CGContextFillRect(context, rect)
        
        //移动坐标
        let x = rect.size.width/2
        let y = rect.size.height/2
        var center = CGPointMake(x,y)
        // 中间扣空
        CGContextClearRect(context, centerRect)
        
        // 绘制正方形框
        CGContextSetStrokeColorWithColor(context, UIColor.whiteColor().CGColor)
        CGContextSetLineWidth(context, mLineSize)
        CGContextAddRect(context, centerRect)
        CGContextDrawPath(context, kCGPathStroke)
        // 绘制4个角
        let cornerWidth = centerRect.width/mCornerLineRatio;
        let cornerHeight = centerRect.height/mCornerLineRatio;
        CGContextSetLineWidth(context, mCornerLineSize)
        CGContextSetStrokeColorWithColor(context, UIColor.greenColor().CGColor)
        // 绘制左上角
        CGContextMoveToPoint(context, centerRect.origin.x, centerRect.origin.y + cornerHeight)
        CGContextAddLineToPoint(context, centerRect.origin.x, centerRect.origin.y)
        CGContextAddLineToPoint(context, centerRect.origin.x + cornerWidth, centerRect.origin.y)
        // 绘制右上角
        CGContextMoveToPoint(context, centerRect.origin.x + centerRect.size.width - cornerWidth, centerRect.origin.y)
        CGContextAddLineToPoint(context, centerRect.origin.x + centerRect.size.width, centerRect.origin.y)
        CGContextAddLineToPoint(context, centerRect.origin.x + centerRect.size.width, centerRect.origin.y + cornerHeight)
        // 绘制右下角
        CGContextMoveToPoint(context, centerRect.origin.x + centerRect.size.width, centerRect.origin.y + centerRect.size.height - cornerHeight)
        CGContextAddLineToPoint(context, centerRect.origin.x + centerRect.size.width, centerRect.origin.y + centerRect.size.height)
        CGContextAddLineToPoint(context, centerRect.origin.x + centerRect.size.width - cornerWidth, centerRect.origin.y + centerRect.size.height)
        // 绘制左下角
        CGContextMoveToPoint(context, centerRect.origin.x, centerRect.origin.y + centerRect.size.height - cornerHeight)
        CGContextAddLineToPoint(context, centerRect.origin.x, centerRect.origin.y + centerRect.size.height)
        CGContextAddLineToPoint(context, centerRect.origin.x + cornerWidth, centerRect.origin.y + centerRect.size.height)
        CGContextDrawPath(context, kCGPathStroke)
    }

到此自定义控件的界面已经完成,这个时候可以看到有一个方形的框在屏幕上了。一篇太长分成两篇来写,继续请看下一篇。