Swift实现二维码扫描与生成

# ****扫描二维码

## ****第三方框架

  • ZXing Android使用多

  • ZBar iOS使用多

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

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

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

## ****识别原理

Swift实现二维码扫描与生成_第1张图片
扫描原理.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)
    }
}

# ****绘制线条

Swift实现二维码扫描与生成_第2张图片
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)
    }
}
  • 转换结果
# 转换前

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"

# 转换后

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实现二维码扫描与生成)