swift学习iOS开发iOS Developer

iOS开发之二维码扫描以及生成

2016-11-27  本文已影响831人  论丶道

简介

特点

功能


目前越来越多的app使用了二维码,且二维码传播更为快捷,方便。
iOS开发中也有诸如ZBarZXing这样优秀的第三方框架,但是其不支持64位。今天就带大家一起来自己动手写一下二维码扫描以及二维码生成的这一功能。

就以新浪微博的二维码扫描界面为例来进行讲解。


一、扫描界面动画

界面这一块我是以storyboard来进行搭建的,想将更多的精力放在二维码扫描以及生成上。


    //冲击波顶部约束
    @IBOutlet weak var scanLineTopConstraint: NSLayoutConstraint!
    //容器视图高度约束
    @IBOutlet weak var containerViewHeightConstraint: NSLayoutConstraint!
//MARK:- 冲击波动画
extension ZDQRCodeViewController {
    func startAnimation() {
        //清空layer上所有动画
        scanfLineView.layer.removeAllAnimations()
        //初始化冲击波位置
        scanLineTopConstraint.constant = -containerViewHeightConstraint.constant
        view.layoutIfNeeded()
        //执行动画
        UIView.animate(withDuration: 2.0) {
            UIView.setAnimationRepeatCount(MAXFLOAT)
            self.scanLineTopConstraint.constant = self.containerViewHeightConstraint.constant
            self.view.layoutIfNeeded()
        }
    }
}

注意:要将容器视图的Clip To Bounds勾上否则动画看起来不流畅

还有一点需要特别注意的:上面定义的startAnimation()这个方法一定要在func viewWillAppear(_ animated: Bool)这个方法里面调用 否则看不到动画效果


二、扫描二维码

识别原理

输入设备与输出数据是通过拍摄会话AVCaptureSession来进行沟通的。
代码实现

 //输入设备
     lazy var inputDevice : AVCaptureDeviceInput? = {
        let device = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo)
        return try? AVCaptureDeviceInput(device: device)
    }()
lazy var session = AVCaptureSession()
lazy var output = AVCaptureMetadataOutput()
 lazy var previewLayer : AVCaptureVideoPreviewLayer = {
        let layer = AVCaptureVideoPreviewLayer(session: self.session)
        return layer!
    }()
//MARK:- 二维码相关
extension ZDQRCodeViewController : AVCaptureMetadataOutputObjectsDelegate {
    func setUpQRCode() {
            //判断是否能将输入设备添加至会话中
        if !session.canAddInput(inputDevice) {
            return
        }
        //判断是否能将输出对象添加至会话中
        if !session.canAddOutput(output) {
            return
        }
        //添加输入输出设备
        session.addInput(inputDevice)
        session.addOutput(output)
        //设置输出对象能够解析的数据类型
        output.metadataObjectTypes = output.availableMetadataObjectTypes
        //设置代理监听解析后的数据
        output.setMetadataObjectsDelegate(self, queue:DispatchQueue.main)
        //添加预览图层
        previewLayer.frame = view.bounds
        view.layer.insertSublayer(previewLayer, at: 0)
        //开始扫描
        session.startRunning()
    }
func captureOutput(captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [AnyObject]!, fromConnection connection: AVCaptureConnection!) {

    print(metadataObjects)
}

三、设置扫描区域

为了能够拥有更好的用户体验,我们应该需要设置一下扫描的区域,让用户将二维码放进界面中的扫描区域内再开始扫描

//输出对象
    lazy var output : AVCaptureMetadataOutput = {
        //创建输出对象
        let metadataOutput = AVCaptureMetadataOutput()
        //获取容器视图的frame
        let containerFrame = self.containerView.frame
        let screenFrame = UIScreen.main.bounds
        //计算比例
        let X = containerFrame.origin.y / screenFrame.size.height
        let Y = containerFrame.origin.x / screenFrame.size.width
        let W = containerFrame.size.height / screenFrame.size.height
        let H = containerFrame.size.width / screenFrame.size.width
        //设置解析数据所感兴趣的区域
        metadataOutput.rectOfInterest = CGRect(x: X, y: Y, width: W, height: H)
        return metadataOutput
    }()

注意:苹果默认为扫描二维码时手机是处于横屏的,所以计算比例时要将X,Y,W,H反过来 要像我上面代码中的计算方式


四、设置描边

  func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [Any]!, from connection: AVCaptureConnection!) {
        for object in metadataObjects {
        let dataObject = previewLayer.transformedMetadataObjectForMetadataObject(object as! AVMetadataObject) as! AVMetadataMachineReadableCodeObject
        print(dataObject)
        }
    }

控制台输出结果为:



我们需要将corners的坐标值进行转换

//MARK:- 生成需要绘制的路径
  func creatPath(corners : [Any]?) -> UIBezierPath? {
        guard let arr = corners  else {
            return nil
        }
        if arr.count == 0 {
            return nil
        }
        var index = 0
        var point = CGPoint.zero
        //取出数组中的一个元素 并将取出的字典转换为CGPoint类型
        point = CGPoint(dictionaryRepresentation: (corners?[index] as! CFDictionary))!
        index += 1
        let path = UIBezierPath()
        path.move(to: point)
        while index < (corners?.count)! {
            //取出数组中的其他元素并将取出的字典转换为CGPoint类型
            point = CGPoint(dictionaryRepresentation: (corners?[index] as! CFDictionary))!
            path.addLine(to: point)
            index += 1
            ZDLog(message: point)
        }
        path.close()
        return path
    }

转换后控制台的打印结果

//MARK:- 绘制描边
 func drawCorners(objc : AnyObject) {
        let metadataObject = previewLayer.transformedMetadataObject(for: objc as! AVMetadataObject)
        let corner = (metadataObject as! AVMetadataMachineReadableCodeObject).corners
        guard let path = creatPath(corners: corner) else {
            return
        }
        let shapeLayer = CAShapeLayer()
        shapeLayer.lineWidth = 5
        shapeLayer.strokeColor = UIColor.green.cgColor
        shapeLayer.fillColor = UIColor.clear.cgColor
        shapeLayer.path = path.cgPath
        containerLayer.addSublayer(shapeLayer)
    }
}

完成这些之后只需要在上文提到的一个解析扫描数据的代理方法中依次调用这些方法就OK了

//MARK:- 解析到扫描的数据
    /* 
    ** 当解析到扫描的数据时会调用
    ** 且所有扫描到的数据都存于metadataObjects中
    */
    func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [Any]!, from connection: AVCaptureConnection!) {
       
        //清空以前的线段
       clearContainerLayer()
        
        for objc in metadataObjects {
            drawCorners(objc: objc as AnyObject)
        }
    }

注意:每次描边之前还需要清空以前的描边线段 否则屏幕上会出现多次描边线段

//MARK:- 清空描边线段
    func clearContainerLayer() {
        if let subLayers = containerLayer.sublayers {
            for layer in subLayers {
                layer.removeFromSuperlayer()
            }
        }
    }

五、读取相册里面的二维码

//MARK:- 相册按钮点击
    @IBAction func photoBtnClick(_ sender: AnyObject) {
        if !UIImagePickerController.isSourceTypeAvailable(.photoLibrary) {
            return
        }
        let picker = UIImagePickerController()
        picker.sourceType = .photoLibrary
        picker.delegate = self
        present(picker, animated: true, completion: nil)
    }
}
//MARK:- 调用相册相关
extension ZDQRCodeViewController : UINavigationControllerDelegate,UIImagePickerControllerDelegate {
   //MARK:- 选中一张图片时调用
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
       //取出选中图片
        guard let image = info[UIImagePickerControllerOriginalImage] as? UIImage else {
            return
        }
        guard let ciImage = CIImage(image: image) else {
            return
        }
        //创建一个探测器
        let dict = [CIDetectorAccuracy : CIDetectorAccuracyHigh]
        let detector = CIDetector(ofType: CIDetectorTypeQRCode, context: nil, options: dict)
        //利用探测器探测结果
        let features = detector?.features(in: ciImage)
        //取出结果
        for result in features! {
            ZDLog(message: (result as! CIQRCodeFeature).messageString)
        }
        //只要实现代理方法,就需要手动关闭浏览器
        picker.dismiss(animated: true, completion: nil)
    }
}

注意:需要同时遵守UINavigationControllerDelegateUIImagePickerControllerDelegate这两个协议


六、生成二维码

 //创建滤镜
        let filter = CIFilter(name: "CIQRCodeGenerator")
        //还原滤镜
        filter?.setDefaults()
        //设置数据
        let data = "测试数据".data(using: String.Encoding.utf8)
        filter?.setValue(data, forKey: "inputMessage")
        //从滤镜中取出数据
        guard var ciImage = filter?.outputImage else {
            return
        }
        //设置图片的清晰度
        let transform = CGAffineTransform(scaleX: 10, y: 10)
        ciImage = ciImage.applying(transform)
        let image = UIImage(ciImage: ciImage)
        //设置图片
        customImageView.image = image

上一篇下一篇

猜你喜欢

热点阅读