二维码

二维码简介

1.概念

  • 二维码:是用某种特定的几何图形按一定规律在平面(二维方向上)分布的黑白相间的图形记录数据符号信息的;
  • 生成二维码:根据给定的信息, 将其按照二维码的编码方式生成一张图片
  • 读取二维码:识别二维码图像里面存储的数据

2.二维码的使用场景

  • 信息获取(名片、WIFI密码、资料)
  • 手机电商(用户扫码、手机直接购物下单)
  • 加好友(QQ, 微信, 扫一扫加好友)
  • 手机支付(扫描商品二维码,通过银行或第三方支付提供的手机端通道完成支付)

3.二维码生成方式

  • 从iOS7开始集成了二维码的生成和读取功能
  • 此前被广泛使用的zbarsdk目前不支持64位处理器
  • 2015年2月1号起, 不允许不支持64位处理器的APP 上架

4.二维码读取

  • 直接从图片中识别,最低支持iOS8.0
  • 利用摄像头扫描识别,需要真机设备

生成二维码

1.导入CoreImage框架 一些图片处理操作的功能, 都是用这个框架实现, 比如: 滤镜效果, 毛玻璃, 美颜相机....

#import <CoreImage/CoreImage.h>

2.通过滤镜CIFilter生成二维码

//创建二维码滤镜

let filter = CIFilter(name: "CIQRCodeGenerator")

//注意,最好恢复滤镜的默认设置
filter?.setDefaults()

//设置滤镜输入数据 KVC

let data = enterField.text.data(using: .utf8)
filter?.setValue(data, forKey: "inputMessage")

//从二维码滤镜终获取结果图片
var image = filter?.outputImage

//处理模糊的图片
let transform = CGAffineTransform.init(scaleX: 20, y: 20)
image = image?.applying(transform)

//显示图片
let resultImage = UIImage(ciImage: image!)

codeImageView.image = resultImage

自定义二维码

所谓自定义二维码, 就是指给二维码添加一些图片(前景或者背景图片), 或者改变下颜色

可以添加前景图片的前提是因为二维码具备一定的"纠错率"

如果二维码被部分遮挡, 可以根据其他部分, 计算出遮挡部分内容;

但是要保证三个角不能被遮挡; 三个角用作扫描定位使用(可能用户倒着拍, 斜着拍等等)

通过KVC 设置滤镜的 inputCorrectionLevel (纠错率) @"L", @"M", @"Q", @"H" 中的一个

  • L水平 7%的字码可被修正
  • M水平 15%的字码可被修正
  • Q水平 25%的字码可被修正
  • H水平 30%的字码可被修正
filter?.setValue("M", forKey: "inputCorrectionLevel")

纠错率越高,扫描花费的时间越多

创建中心图片

func createCenterImage(qrCodeImage:UIImage, centerImage:UIImage) -> UIImage?{

    UIGraphicsBeginImageContext(qrCodeImage.size)

    qrCodeImage.draw(in: CGRect(x: 0, y: 0, width: qrCodeImage.size.width, height: qrCodeImage.size.height))

    let centerImageWH:CGFloat = 100.0
    centerImage.draw(in: CGRect(x: (qrCodeImage.size.width - centerImageWH) * 0.5, y: (qrCodeImage.size.height - centerImageWH) * 0.5, width: centerImageWH, height: centerImageWH))

    let resultImage = UIGraphicsGetImageFromCurrentImageContext()

    UIGraphicsEndImageContext()

    return resultImage
}

识别二维码

一个完整的示例

import UIKit

class QRCodeRecognitionController: UIViewController {

    @IBOutlet weak var imageView: UIImageView!
    @IBOutlet weak var tableView: UITableView!

    lazy var datas = [[String:Any]]()

    override func viewDidLoad() {
        super.viewDidLoad()

        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
        tableView.dataSource = self
        tableView.delegate = self
    }

    @IBAction func recognition(_ sender: UIButton) {

        //获取需要识别的图片
        var image = imageView.image

        //识别
        //创建一个二维码探测器
        let dector = CIDetector(ofType: CIDetectorTypeQRCode, context: nil, options: [CIDetectorAccuracy : CIDetectorAccuracyHigh])

        //直接探测二维码的特征,获得的是个数组,因为有可能右多个二维码图片
        let features = dector?.features(in: CIImage(image: image!)!)

        datas = []

        //解析每一个二维码特征,获取一些关键信息
        for feature in features! {
            let ciFeature = feature as! CIQRCodeFeature
            let recogImage = drawQRCodeImage(bounds:ciFeature.bounds,sourceImage:image!)//注意,这个bounds的坐标系是上下颠倒的

            datas.append(["image":recogImage!,"message":ciFeature.messageString ?? "无法识别"])
        }
        tableView.reloadData()

    }

    func drawQRCodeImage(bounds:CGRect,sourceImage:UIImage) -> UIImage? {

        print("sourceImage size:\(sourceImage.size)")
        print("bounds:\(bounds)")

        UIGraphicsBeginImageContext(bounds.size)

        sourceImage.draw(in: CGRect(x: -bounds.origin.x, y: -(sourceImage.size.height - bounds.height - bounds.origin.y), width: sourceImage.size.width, height: sourceImage.size.height))

        let image = UIGraphicsGetImageFromCurrentImageContext()

        UIGraphicsEndImageContext()

        return image
    }
}

extension QRCodeRecognitionController:UITableViewDataSource, UITableViewDelegate {

    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return datas.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)

        cell.imageView?.image = datas[indexPath.row]["image"] as? UIImage
        cell.textLabel?.text = datas[indexPath.row]["message"] as? String

        return cell
    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let str = datas[indexPath.row]["message"] as? String

        if str == nil { return }

        if str!.contains("http") {

            UIApplication.shared.open(URL(string:str!)!, options: [:], completionHandler: nil)
        }

    }
}

扫描二维码

一个完整的示例

import UIKit
import AVFoundation

class QRCodeScanController: UIViewController {

    var session:AVCaptureSession?

    var layer:AVCaptureVideoPreviewLayer?

    var resultStr:String?

    @IBOutlet weak var containerView: UIView!
    @IBOutlet weak var borderImageView: UIImageView!
    @IBOutlet weak var scanlineImageView: UIImageView!
    @IBOutlet weak var bottomCons: NSLayoutConstraint!

    override func viewDidLoad() {
        super.viewDidLoad()

        containerView.clipsToBounds = true

        configScan()
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        startAnimation()

        removeFrame()

        //启动会话,让输入开始采集数据,输出对象,开始处理数据
        session?.startRunning()
    }

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {

        if segue.destination.isKind(of: QRScanResultController.self) {

            (segue.destination as! QRScanResultController).text = resultStr!
        }
    }

}

extension QRCodeScanController {

    func startAnimation() {

        bottomCons.constant = containerView.frame.height
        view.layoutIfNeeded()
        bottomCons.constant = -containerView.frame.height

        UIView.animate(withDuration: 2) {

            UIView.setAnimationRepeatCount(MAXFLOAT)
            self.view.layoutIfNeeded()
        }
    }
}

extension QRCodeScanController: AVCaptureMetadataOutputObjectsDelegate {

    func configScan() {

        //设置输入
        //获取摄像头设备
        let device = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo)
        //把摄像头设备当做输入设备
        let input = try? AVCaptureDeviceInput(device: device)

        guard input != nil else { return }

        //设置输出
        let output = AVCaptureMetadataOutput()

        //设置结果处理的代理
        output.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)

        //创建会话,连接输入和输出
        session = AVCaptureSession()
        guard (session?.canAddInput(input))! && (session?.canAddOutput(output))!
            else { return }
        session?.addInput(input)
        session?.addOutput(output)

        //设置可以二维码的码制, 必须在输出添加到会话之后才可以设置,否则会崩溃
        output.metadataObjectTypes = [AVMetadataObjectTypeQRCode]

        //设置指定的区域扫描
        let screenBounds = UIScreen.main.bounds
        let outX:CGFloat = (containerView.frame.origin.x - 20) / screenBounds.size.width
        let outY:CGFloat = (containerView.frame.origin.y - 20 + 44) / screenBounds.size.height
        let outW:CGFloat = (containerView.frame.size.width + 20) / screenBounds.size.width
        let outH:CGFloat = (containerView.frame.size.height + 20 + 44) / screenBounds.size.height
        output.rectOfInterest = CGRect(x: outY, y: outX, width: outH, height: outW)

        //添加视频预览图层
        layer = AVCaptureVideoPreviewLayer(session: session!)
        layer?.frame = view.layer.bounds;
        view.layer.insertSublayer(layer!, at: 0)
    }

    func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [Any]!, from connection: AVCaptureConnection!) {

        removeFrame()

        for meta in metadataObjects {
            if (meta as AnyObject).isKind(of:AVMetadataMachineReadableCodeObject.self) {
                let resultObj = layer?.transformedMetadataObject(for: meta as! AVMetadataObject)
                let qrCodeObj = resultObj as! AVMetadataMachineReadableCodeObject
                //print(qrCodeObj.stringValue)
                resultStr = qrCodeObj.stringValue
                //print(qrCodeObj.corners)//代表二维码的4个角,但是一个比例数值,需要转换
                drawFrame(qrCodeObj: qrCodeObj)

                if resultStr != nil {

                    session?.stopRunning()

                    print("resultStr:\(resultStr!)")
                    performSegue(withIdentifier: "scanResult", sender: nil)
                }
            }
        }
    }

    func drawFrame(qrCodeObj:AVMetadataMachineReadableCodeObject) {

        let corners = qrCodeObj.corners

        //创建一个图形层用于绘制
        let shapeLayer = CAShapeLayer()
        shapeLayer.fillColor = UIColor.clear.cgColor
        shapeLayer.strokeColor = UIColor.green.cgColor
        shapeLayer.lineWidth = 3

        //根据四个点创建一个路径
        let path = UIBezierPath()

        var index = 0
        for corner in corners! {

            let pointDict = corner as! CFDictionary
            let point = CGPoint(dictionaryRepresentation: pointDict)

            if index == 0 {
                path.move(to: point!)
            } else {
                path.addLine(to: point!)
            }

            index += 1
        }

        path.close()

        shapeLayer.path = path.cgPath

        layer?.addSublayer(shapeLayer)
    }

    func removeFrame() {
        guard let sublayers = layer?.sublayers else { return }

        for sublayer in sublayers {
            if sublayer.isKind(of: CAShapeLayer.self) {
                sublayer.removeFromSuperlayer()
            }
        }
    }
}

results matching ""

    No results matching ""