Swift中二维码的相关操作

二维码

  • 二维码概念
    • 二维码是用某种特点的集合图形按一定规律在平面(二维方向上)分布的黑白相间的图形,用于记录数据符号信息;
  • 二维码的使用场景
    • 信息获取(名片,WIFI密码,资料)
    • 手机电商(用户扫码,手机直接购物下单)
    • 加好友(QQ,微信,扫一扫加好友)
    • 手机支付(扫描商品二维码,永固欧银行或第三方支付提供的手机端通道完成支付)
  • 二维码的生成方式
    • 从iOS7开始继承了二维码的生成和读取功能
    • 此前被广泛使用的zbars和zxing目前不支持64位处理器(5s以后都是64位处理器)(2015年2月1号开始,不允许不支持64位处理器的APP上架)

生成二维码

  • 步骤
    1. 获取文本框输入的内容
    2. 生成一个二维码滤镜(不同的字符串生成不同的滤镜)
    3. 设置二维码的内容
    4. 从二维码滤镜中获取生成的二维码
    5. 展示图片(注意:默认生成的二维码大小为23,放大图片后就会显得模糊)

生成二维码实例

/// 生成二维码
extension QRCodeTool {
    /// 生成二维码
    ///
    /// - Parameters:
    ///   - contentStr: 二维码内容
    ///   - bigImageWH: 二维码大小
    ///   - smallImage: 小图标
    ///   - smallImageSize: 小图标大小
    /// - Returns: 生成一个自顶一个二维码图片返回
    class func gerneratorQRCode(contentStr : String, bigImageWH :  CGFloat, smallImage : UIImage, smallImageSize : CGFloat) -> UIImage? {
        // 1.创建一个二维码滤镜
        guard let filter = CIFilter(name: "CIQRCodeGenerator") else {return nil}
        
        // 1.1恢复默认值
        filter.setDefaults()
        
        // 2.设置内容
        // 注意点
        // 如果要设置二维码内容,必须以KVC的方式设置
        // 二维码的值必须是NSData
        let data = contentStr.data(using: .utf8)
        filter.setValue(data, forKeyPath: "inputMessage")
        
        // 2.1设置二维码的级别(纠错率)
        //        key : inputCorrectionLevel
        //        value L: 7% M(默认  ): 15% Q: 25% H: 30%
        filter.setValue("H", forKeyPath: "inputCorrectionLevel")
        
        // 3.从二维码滤镜中获取二维码
        guard let outputImage = filter.outputImage else {return nil}
        
        // 4.展示图片(生成的二维码图片的大小为23*23,放大到200*200就会变得很模糊)
        //        let imageUI = UIImage(ciImage: outputImage)
        //        print(imageUI.size)
        guard let imageUI = createClearImage(outputImage, size: bigImageWH) else {return nil}
        
        // 4.设置二维码颜色(注意:设置二维码颜色,必须放在清晰的二维码之后,如果先设置颜色,再改变清晰度,则设置颜色无效)
        let imageCI = CIImage(image: imageUI)
        let colorFiler = CIFilter(name: "CIFalseColor")
        colorFiler?.setDefaults()
        // 设置图片
        colorFiler?.setValue(imageCI, forKeyPath: "inputImage")
        // 设置二维码亚瑟
        colorFiler?.setValue(CIColor.init(color: UIColor.yellow), forKeyPath: "inputColor0")
        // 设置背景颜色
        colorFiler?.setValue(CIColor.init(color: UIColor.red), forKeyPath: "inputColor1")
        guard let colorOutputImage = colorFiler?.outputImage else {return nil}
        let image  = UIImage(ciImage: colorOutputImage)
        return createCustomImage(bigImage: image, smallImage: smallImage, smallImageWH: smallImageSize)
    }

    /// 自定义二维码
    ///
    /// - Parameters:
    ///   - bigImage: 二维码图片
    ///   - smallImage: 小图片
    ///   - smallImageWH: 小图片尺寸
    /// - Returns: 生成新的二维码图片
    fileprivate class func createCustomImage(bigImage : UIImage, smallImage : UIImage, smallImageWH : CGFloat) -> UIImage? {
        
        // 0.大图片的尺寸
        let bigImageSize = bigImage.size
        
        // 1.创建图形上下文
        UIGraphicsBeginImageContext(bigImageSize)
        
        // 2.绘制大图片
        bigImage.draw(in: CGRect(x: 0, y: 0, width: bigImageSize.width, height: bigImageSize.height))
        
        // 3.绘制小图片
        smallImage.draw(in: CGRect(x: (bigImageSize.width - smallImageWH) * 0.5, y: (bigImageSize.height - smallImageWH) * 0.5, width: smallImageWH, height: smallImageWH))
        
        // 4.从图形上下文中取出图片
        let image = UIGraphicsGetImageFromCurrentImageContext()
        
        // 5.关闭图形上下文
        UIGraphicsEndImageContext()
        
        // 6.返回图片
        return image
        
    }
    
    /// 将模糊的二维码图片转为清晰的
    ///
    /// - parameter image: 模糊的二维码图片
    /// - parameter size: 需要生成的二维码尺寸
    ///
    /// - returns: 清晰的二维码图片
    fileprivate class func createClearImage(_ image : CIImage, size : CGFloat ) -> UIImage? {
        
        // 1.调整小数像素到整数像素,将origin下调(12.*->12),size上调(11.*->12)
        let extent = image.extent.integral
        
        // 2.将指定的大小与宽度和高度进行对比,获取最小的比值
        let scale = min(size / extent.width, size/extent.height)
        
        // 3.将图片放大到指定比例
        let width = extent.width * scale
        let height = extent.height * scale
        
        // 3.1创建依赖于设备的灰度颜色通道
        let cs = CGColorSpaceCreateDeviceGray();
        
        // 3.2创建位图上下文
        let bitmapRef = CGContext(data: nil, width: Int(width), height: Int(height), bitsPerComponent: 8, bytesPerRow: 0, space: cs, bitmapInfo: 0)
        
        // 4.创建上下文
        let context = CIContext(options: nil)
        
        // 5.将CIImage转为CGImage
        let bitmapImage = context.createCGImage(image, from: extent)
        
        // 6.设置上下文渲染等级
        bitmapRef!.interpolationQuality = .none
        
        // 7.改变上下文的缩放
        bitmapRef?.scaleBy(x: scale, y: scale)
        
        // 8.绘制一张图片在位图上下文中
        bitmapRef?.draw(bitmapImage!, in: extent)
        
        // 9.从位图上下文中取出图片(CGImage)
        guard let scaledImage = bitmapRef?.makeImage() else {return nil}
        
        // 10.将CGImage转为UIImage并返回
        return UIImage(cgImage: scaledImage)
    }
}

效果

识别二维码

  • 步骤
    1. 创建二维码探测器
    2. 探测图片特征
    3. 读取图片特征

识别二维码实例

extension QRCodeTool {
    /// 识别图片中的二维码
    ///
    /// - Parameter sourceImage: 原始图片
    /// - Returns: 将识别到的二维码绘制边框并返回图片
    class func detectorQRCode(sourceImage : UIImage) -> UIImage? {
        // 0.创建上下文
        let context = CIContext()
        
        // 1.创建二维码的探测器
        guard let detector = CIDetector(ofType: CIDetectorTypeQRCode, context: context, options: [CIDetectorAccuracy : CIDetectorAccuracyHigh]) else {return nil}
        
        // 2.探测图片
        guard let sourceImageCI = CIImage(image: sourceImage) else {return nil}
        guard let features = detector.features(in: sourceImageCI) as? [CIQRCodeFeature] else {return nil}
        
        // 3.绘制识别的二维码的边框
        return drawQRCodeBorder(features: features, sourceImage: sourceImage)
    }
    
    fileprivate class func drawQRCodeBorder(features : [CIQRCodeFeature], sourceImage : UIImage) -> UIImage? {
        
        // 0.源图片大小
        let sourceImageSize = sourceImage.size
        
        // 1.创建图形上下文
        UIGraphicsBeginImageContext(sourceImageSize)
        
        // 2.绘制图片
        sourceImage.draw(in: CGRect(x: 0, y: 0, width: sourceImageSize.width, height: sourceImageSize.height))
        
        // 3.绘制边框(bounds的坐标原点在左下角)
        // 第二种解决方式
        // 获取图形上下文
        let context = UIGraphicsGetCurrentContext()
        
        // 改变图形上下文的坐标原点
        // 注意:必须放在绘制之前
        context?.scaleBy(x: 1, y: -1)
        context?.translateBy(x: 0, y: -sourceImageSize.height)
        for feature in features {
            let bounds = feature.bounds
            // 第一种解决方式
            // let newBounds = CGRect(x: bounds.origin.x, y:sourceImageSize.height - bounds.origin.y - bounds.height , width: bounds.width, height: bounds.height)
            
            // let path = UIBezierPath(rect: newBounds)
            let path = UIBezierPath(rect: bounds)
            UIColor.red.set()
            path.lineWidth = 4
            path.stroke()
        }
        
        // 4.取出图片
        let image = UIGraphicsGetImageFromCurrentImageContext()
        
        // 5.关闭图形上下文
        UIGraphicsEndImageContext()
        
        // 6.返回图片
        return image
    }
}

效果

扫描二维码

  • 扫描功能
    1. 功能描述
      • 使用摄像头扫描图片中的二维码,获取二维码内的数据
    2. 扫描二维码原理
      1. 需要使用AVFoundation
      2. 获取摄像头,作为输入设备
      3. 创建输出对象,并设置代理,扫描到结果后就会通过代理告诉外界
      4. 创建捕捉会话,将输入和输出连接起来
      5. 开启会话,执行扫描操作
    3. 注意点
      1. 访问用户的摄像头需要请求授权
        • info.plist配置Privacy - Camera Usage Description
      2. 在使用输出设备设置代理以及队列时注意传的是主队列还是全局队列
      3. 使用会话连接输入和输出之前先判断是否可以添加
      4. 必须制定扫描的类型,并在将输出添加到会话后再添加,否则会崩溃
      5. 保证捕捉会话不被销毁
      6. 当扫描结果时会不断的调用代理方法
class QRCodeTool: NSObject {
    /// 扫描二维码
    
    // 单例对象
    static let shareInstance = QRCodeTool()
    
    ///懒加载
    /// 创建会话(顺便添加输入和输出对象)
    fileprivate lazy var session : AVCaptureSession? = {
        // 1.获取摄像头设备
        guard let captureDevice = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo) else {return nil}
        
        // 2.将摄像头作为输入对象
        var input : AVCaptureDeviceInput?
        do {
            input = try AVCaptureDeviceInput(device: captureDevice)
            
        } catch {
            print(error)
            return nil
        }
        
        // 3.创建输出对象
        let output = AVCaptureMetadataOutput()
        output.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
        
        // 4.创建捕捉会话
        let session = AVCaptureSession()
        
        // 5.添加输入对象和输出对象到会话中
        if session.canAddInput(input!) && session.canAddOutput(output)  {
            session.addInput(input!)
            session.addOutput(output)
            
            // 注意:如果要扫描任何码制,必须制定扫描的类型
            // 必须在添加了输出对象之后再设置
            output.metadataObjectTypes = [AVMetadataObjectTypeQRCode]
        }
        return session
    }()
    
    /// 创建预览图层
    /// 将预览图层添加到layer上,注意添加到最底层,否则会遮住当前的控件
    fileprivate lazy var preViewLayer : AVCaptureVideoPreviewLayer? = {
        guard let preViewLayer = AVCaptureVideoPreviewLayer(session: self.session) else {return nil}
        return preViewLayer
    }()
    
    // 定义闭包
    typealias ScanResultBlock = (_ resultStr : String) -> ()
    
    var scanResultBlock : ScanResultBlock?
    
    func scanQRCode(scanView : UIView, preView : UIView, scanResultBlock : @escaping ScanResultBlock) -> () {
        
        // 0.记录block
        self.scanResultBlock = scanResultBlock
        
        // 1.判断会话和预览图层是否有值
        if session == nil || preViewLayer == nil {
            return
        }
        
        if let subLayers = preView.layer.sublayers {
            let isHavePreViewLayer = subLayers.contains(preViewLayer!)
            if !isHavePreViewLayer {
                preView.layer.insertSublayer(preViewLayer!, at: 0)
                preViewLayer?.frame = preView.bounds
                
                guard let output = session!.outputs.first as? AVCaptureMetadataOutput  else {
                    return
                }
                // 设置扫描区域(内部会以坐标原点在右上角设置)
                let x = scanView.frame.origin.y / preView.bounds.height
                let y = scanView.frame.origin.x / preView.bounds.width
                let w = scanView.bounds.height / preView.bounds.height
                let h = scanView.bounds.width / preView.bounds.width
                output.rectOfInterest = CGRect(x: x, y: y, width: w, height: h)
            }
        }
        // 2.判断是否正在扫描
        if session!.isRunning {
            return
        }
        // 3.开始扫描
        session!.startRunning()
    }
    
    
    /// 控制手机手电筒
    /// 步骤 
    /// 1.需要使用AVFoundation框架
    /// 2.获取手机摄像头
    /// 3.通过摄像头获取硬件的控制权
    /// 4.设置手电筒的模式
    /// 5.取消硬件的控制权
    class func turnLight(isOn : Bool) {
        
        // 1.获取摄像头
        guard let captureDevice = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo) else {return}
        
        // 2.获取设备的控制权
        do {
            try captureDevice.lockForConfiguration()
        } catch {
            print(error)
            return
        }
        
        if isOn { // 打开手电筒
            captureDevice.torchMode = .on
            
        } else { // 关闭手电筒
            captureDevice.torchMode = .off
        }
        
        // 3.释放设备的控制权
        captureDevice.unlockForConfiguration()
    }
}

/// AVCaptureMetadataOutputObjectsDelegate
extension QRCodeTool : AVCaptureMetadataOutputObjectsDelegate {
    
    /// 当扫描到结果的时候就会来到该方法(任何码制都会来到该方法)
    ///
    /// - Parameters:
    ///   - captureOutput: 输出
    ///   - metadataObjects: 扫描到码制数组
    ///   - connection: 链接
    func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [Any]!, from connection: AVCaptureConnection!) {
        
        removeQRCodeBorder()
        
        // 扫描的类型AVMetadataMachineReadableCodeObject(二维码类型)
        // 这里只处理二维码
        // 当移除屏幕的时候,系统会额外调用一次该方法,内容是空
        
        guard let result = metadataObjects.first as? AVMetadataMachineReadableCodeObject else {return}
        scanResultBlock!(result.stringValue)
        drawQRCodeBorder(result: result)
        
//        guard let results = metadataObjects as? [AVMetadataMachineReadableCodeObject] else {return}
//        for result in results {
//            //            print(result.stringValue, result.corners)
//            drawQRCodeBorder(result: result)
//        }
        
    }
    
    /// 给二维码绘制边框
    ///
    /// - Parameter result: 扫描到的二维码结果
    private func drawQRCodeBorder(result : AVMetadataMachineReadableCodeObject) {
        
        // result.corners:是数据坐标,必须使用预览图层将坐标转为位(上下文)的坐标
        guard let resultObj = preViewLayer?.transformedMetadataObject(for: result) as? AVMetadataMachineReadableCodeObject else {return}
        // 绘制边框
        let path = UIBezierPath()
        var index = 0
        for corner in resultObj.corners {
            
            let dictCF = corner as! CFDictionary
            let point = CGPoint(dictionaryRepresentation: dictCF)!
            
            // 开始绘制
            if index == 0 {
                path.move(to: point)
            } else {
                path.addLine(to: point)
            }
            
            index = index + 1
        }
        
        // 关闭路径
        path.close()
        
        // 创建形状图层
        let shapeLayer = CAShapeLayer()
        shapeLayer.path = path.cgPath
        shapeLayer.lineWidth = 3
        shapeLayer.strokeColor = UIColor.red.cgColor
        shapeLayer.fillColor = UIColor.clear.cgColor
        preViewLayer?.addSublayer(shapeLayer)
    }
    
    /// 移除边框
    private func removeQRCodeBorder() {
        
        if let layers = preViewLayer?.sublayers {
            for layer in layers {
                let shapeLayer = layer as? CAShapeLayer
                if shapeLayer != nil {
                    shapeLayer?.removeFromSuperlayer()
                }
            }
        }   
    }
}

效果

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,290评论 6 491
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,107评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,872评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,415评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,453评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,784评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,927评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,691评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,137评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,472评论 2 326
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,622评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,289评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,887评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,741评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,977评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,316评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,490评论 2 348

推荐阅读更多精彩内容