Swift实现二维码扫描与生成

# ****扫描二维码

## ****第三方框架

  • 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地址

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

推荐阅读更多精彩内容