美文网首页
Swift实现二维码扫描与生成

Swift实现二维码扫描与生成

作者: 是我始终拒绝成长吗 | 来源:发表于2016-04-29 15:56 被阅读1812次

    # ****扫描二维码

    ## ****第三方框架

    • ZXing Android使用多

    • ZBar iOS使用多

    • 提示:以上两个框架都是老牌二维码框架,不过都不支持 64 位

    • 目前在 iOS 开发中普遍使用苹果的 AVFoundation 框架,但是不支持图片识别功能

    • AVFoundation 只支持通过摄像头扫描识别

    ## ****识别原理

    扫描原理.png

    g)

    ## ****代码实现

    • 拍摄会话
    /// 拍摄会话,是扫描的桥梁
    lazy var session: AVCaptureSession = {
        return AVCaptureSession()
    }()
    
    • 摄像头输入设备
    // 2. 输入设备
    lazy var inputDevice: AVCaptureDeviceInput? = {
    
        let device = AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeVideo)
        do {
            return try AVCaptureDeviceInput(device: device)
        } catch {
            print(error)
            return nil
        }
    }()
    
    • 数据输出
    /// 数据输出
    lazy var dataOutput: AVCaptureMetadataOutput = {
        return AVCaptureMetadataOutput()
    }()
    
    • 建立通道,设置会话
    private func setupSession() {
        // 1. 判断是否能够添加设备
        if !session.canAddInput(inputDevice) {
            print("无法添加输入设备")
            return
        }
    
        // 2. 判断能否添加输出数据
        if !session.canAddOutput(outputData) {
            print("无法添加输出数据")
            return
        }
    
        // 3. 添加设备
        session.addInput(inputDevice)
        session.addOutput(outputData)
        print(outputData.availableMetadataObjectTypes)
    
        // 4. 设置扫描数据类型
        outputData.metadataObjectTypes = outputData.availableMetadataObjectTypes
        // 5. 设置输出代理
        outputData.setMetadataObjectsDelegate(self, queue: dispatch_get_main_queue())
    }
    

    注意,一定要把输出设备添加到会话后,才有可用数据类型

    • 实现协议方法
    // MARK: - AVCaptureMetadataOutputObjectsDelegate
    func captureOutput(captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [AnyObject]!, fromConnection connection: AVCaptureConnection!) {
    
        println(metadataObjects)
    }
    

    必须要启动会话,才能开始扫描

    ///  开始扫描
    private func startScan() {
        session.startRunning()
    }
    
    • 添加预览视图
    /// 预览图层
    lazy var previewLayer: AVCaptureVideoPreviewLayer = {
        return AVCaptureVideoPreviewLayer(session: self.session)
    }()
    
    • 设置图层
    /// 设置图层
    func setupLayers() {
        previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
        previewLayer.frame = view.bounds
        view.layer.insertSublayer(previewLayer, atIndex: 0)
    }
    

    进一步体会一下此处的 self.

    • 闭包内部需要使用 self.

    • OC 中使用 self. 能够调用属性的 getter 方法,确保对象一定能够拿到

    • 修改扫描代理方法,提取数值

    // MARK: - 扫描数据代理
    func captureOutput(captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [AnyObject]!, fromConnection connection: AVCaptureConnection!) {
    
        for object in metadataObjects {
            print(object)
        }
    }
    

    # ****绘制线条

    Bounds&Corners.png
    ## AVMetadataMachineReadableCodeObject
    • bounds
    • corners

    ## ****代码实现

    • 坐标转换
    // MARK: - 扫描数据代理
    func captureOutput(captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [AnyObject]!, fromConnection connection: AVCaptureConnection!) {
    
        for object in metadataObjects {
            let dataObject = previewLayer.transformedMetadataObjectForMetadataObject(object as! AVMetadataObject) as! AVMetadataMachineReadableCodeObject
    
            print(dataObject)
        }
    }
    
    • 转换结果
    # 转换前
    <AVMetadataMachineReadableCodeObject: 0x170220720,
    type="org.iso.QRCode",
    bounds={ 0.4,0.4 0.1x0.2 }>
    corners { 0.4,0.6 0.5,0.6 0.5,0.4 0.4,0.4 },
    time 155921691680958,
    stringValue "http://weibo.cn/qr/userinfo?uid=5365823342"
    
    # 转换后
    <AVMetadataMachineReadableCodeObject: 0x170622cc0,
    type="org.iso.QRCode",
    bounds={ 116.6,224.9 79.5x80.0 }>
    corners { 116.6,226.1 117.2,304.4 196.1,304.9 195.7,224.9 },
    time 155921691680958,
    stringValue "http://weibo.cn/qr/userinfo?uid=5365823342"
    

    转换的目的是将采集到的坐标转换成能够识别的坐标数值

    • 绘制图层
    /// 绘制图层
    lazy var drawLayer: CALayer = {
        return CALayer()
    }()
    
    • 添加图层
    /// 设置图层
    func setupLayers() {
        drawLayer.frame = view.bounds
        view.layer.insertSublayer(drawLayer, atIndex: 0)
    
        previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
        previewLayer.frame = view.bounds
        view.layer.insertSublayer(previewLayer, atIndex: 0)
    }
    

    注意:一定要用 insertSublayer,否则会遮挡住 TabBar

    • 创建路径 & 绘制条码形状
    /// 绘制条码形状
    private func drawCornersShape(dataObject: AVMetadataMachineReadableCodeObject) {
        // 判断数组是否为空
        if dataObject.corners.isEmpty {
            return
        }
    
        let layer = CAShapeLayer()
        layer.lineWidth = 4
        layer.strokeColor = UIColor.greenColor().CGColor
        layer.fillColor = UIColor.clearColor().CGColor
        layer.path = cornersPath(dataObject.corners)
    
        // 添加到绘图图层
        drawLayer.addSublayer(layer)
    }
    
    ///  创建边线路径
    ///
    ///  -parameter corners: 边角顶点数组
    private func cornersPath(corners: NSArray) -> CGPathRef {
        let path = UIBezierPath()
        var point = CGPoint()
    
        // 1. 移动到第一个点
        var index = 0
        CGPointMakeWithDictionaryRepresentation((corners[index++] as! CFDictionaryRef), &point)
        path.moveToPoint(point)
    
        // 2. 遍历剩余的点
        while index < corners.count {
            CGPointMakeWithDictionaryRepresentation((corners[index++] as! CFDictionaryRef), &point)
            path.addLineToPoint(point)
        }
    
        // 3. 关闭路径
        path.closePath()
    
        return path.CGPath
    }
    

    注意

    • corners 是保存 CFDictionary 对象的数组

    • 一定要判断 corners 是否包含数据,否则会崩溃

    • 清空绘图图层

    /// 清空绘图图层
    private func clearDrawLayer() {
        if drawLayer.sublayers == nil {
            return
        }
    
        for layer in drawLayer.sublayers! {
            layer.removeFromSuperlayer()
        }
    }
    

    注意:一定要判断 subLayers 否则会崩溃

    • 调整后的代码
    func captureOutput(captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [AnyObject]!, fromConnection connection: AVCaptureConnection!) {
    
        clearDrawLayer()
    
        for object in metadataObjects {
    
            if object is AVMetadataMachineReadableCodeObject {
                let dataObject = previewLayer.transformedMetadataObjectForMetadataObject(object as! AVMetadataObject) as! AVMetadataMachineReadableCodeObject
    
                drawCornersShape(dataObject)
                print(dataObject.stringValue)
            }
        }
    }
    

    一定要判断一下 object 的类型,否则遇到非 CodeObject 会直接崩溃

    # ****生成二维码

    /// 生成二维码
    private func generateQRCodeImage() -> UIImage {
    
        // 1. 生成二维码
        let qrFilter = CIFilter(name: "CIQRCodeGenerator")!
        qrFilter.setDefaults()
        qrFilter.setValue("任玉飞".dataUsingEncoding(NSUTF8StringEncoding), forKey: "inputMessage")
        let ciImage = qrFilter.outputImage
    
        // 2. 缩放处理
        let transform = CGAffineTransformMakeScale(10, 10)
        let transformImage = ciImage.imageByApplyingTransform(transform)
    
        // 3. 颜色滤镜
        let colorFilter = CIFilter(name: "CIFalseColor")!
        colorFilter.setDefaults()
        colorFilter.setValue(transformImage, forKey: "inputImage")
        // 前景色
        colorFilter.setValue(CIColor(color: UIColor.blackColor()), forKey: "inputColor0")
        // 背景色
        colorFilter.setValue(CIColor(color: UIColor.whiteColor()), forKey: "inputColor1")
    
        let outputImage = colorFilter.outputImage
    
        return insertAvatarImage(UIImage(CIImage: outputImage), avatar: UIImage(named: "avatar")!)
    }
    
    • 插入头像
    func insertAvatarImage(qrimage: UIImage, avatar: UIImage) -> UIImage {
    
        UIGraphicsBeginImageContext(qrimage.size)
    
        let rect = CGRect(origin: CGPointZero, size: qrimage.size)
        qrimage.drawInRect(rect)
    
        let w = rect.width * 0.2
        let x = (rect.width - w) * 0.5
        let y = (rect.height - w) * 0.5
        avatar.drawInRect(CGRect(x: x, y: y, width: w, height: w))
    
        let image = UIGraphicsGetImageFromCurrentImageContext()
    
        UIGraphicsEndImageContext()
    
        return image
    }
    

    查阅滤镜 print(CIFilter.filterNamesInCategory(kCICategoryBuiltIn))

    代码地址

    github地址

    相关文章

      网友评论

          本文标题:Swift实现二维码扫描与生成

          本文链接:https://www.haomeiwen.com/subject/wptkrttx.html