(原文:Building a QR Code Reader in Swift 作者:Simon Ng 译者:xiaoying )
我相信大多数人都知道二维码(QR code)是什么,如果你对这个概念还不甚了解,那么看看下边那张图就知道了。


维码是在二维平面里展示的一种条形码,开发者是Denso。最初它只是在制造业用来进行零部件跟踪,但是随着时间的发展,今天二维码已经在消费领域变得非
常流行,在消费领域二维码通常会被用来编码一个登录页面或者推广页面的URL。与传统的条形码不同的是,二维码在水平和垂直方向上都可以存储信息,这样做
的直接好处就是在二维码里可以同时以数字和字符的格式存储大量的信息。但是在这里我不会去探讨太多二维码的技术细节。如果感兴趣,可以去二维码的官方网站了解更多信息。

最近几年,二维码的应用不断的在增多。它可能出现在杂志、报纸、广告、广告板甚至出现在名片上。作为一个iOS开发者,你可能在想如何才能让你的应用具备识别二维码的功能呢。不久之前,Gabriel写了一篇很好的二维码入门指南。在本篇文章里,我们将使用Swift构建一个相似功能的二维码扫描器应用。在阅读完这篇文章之后,你就会了解怎么使用AVFoundation框架实时地去检测和识别二维码。

那么我们这就开工了。

Demo应用


们要构建的这个demo应用相当的简单且直观,但是在开始讨论这个demo应用之前,我们要知道,在iOS里任何条码的扫描都是完全基于视频捕捉的,这很
重要,这也是为什么我们要在含有二维码扫描的应用里加入AVFoundation框架。记住这一点,会对理解整个章节很有帮助。

那么,这个demo应用是如果工作的呢?

看一下下面这张截图,这就是这个应用的UI的样子。这个应用其实就像一个普通的视频捕捉应用,只是没有录像功能。当应用启动之后,它利用iPhone的摄像头来对准二维码,然后二维码会自动被识别,解码后的信息(例如,一个URL)就会显示在屏幕的下方。

我已经预先创建好了一个模板工程,并将storyboard和显示信息的label都帮你连接好了,你可以先从这里下载这个工程。

使用AVFoundation框架

在上面下载的模板工程里,我已经创建了这个应用的用户接口。在UI界面的那个label会被用来显示二维码解码后的信息,这个label已经和ViewController里的messageLabel这个属性进行绑定。

就像之前说过的,我们要依靠AVFoundation框架来实现二维码扫描的功能,所以首先,打开ViewController.swift文件,导入框架:

import AVFoundation

然后,我们需要实现AVCaptureMetadataOutputObjectsDelegate协议,我们一会儿再说这个协议,现在,只要在代码里加上这一行:

class ViewController: UIViewController, AVCaptureMetadataOutputObjectsDelegate

在继续之前,先在ViewController类里定义一下变量,我们之后将会挨个讲解它们:

var captureSession:AVCaptureSession?
var videoPreviewLayer:AVCaptureVideoPreviewLayer?
var qrCodeFrameView:UIView?

实现视频捕获

就像在前面一 段提到的,二维码的读取完全是基于视频捕获的,那么为了实时捕获视频,我们只需要以合适的AVCaptureDevice对象作为输入参数去实例化一个 AVCaptureSession对象。在ViewController类的viewDidLoad方法中加入如下代码:

// Get an instance of the AVCaptureDevice class to initialize a device object and provide the video
// as the media type parameter.
let captureDevice = AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeVideo) // Get an instance of the AVCaptureDeviceInput class using the previous device object.
var error:NSError?
let input: AnyObject! = AVCaptureDeviceInput.deviceInputWithDevice(captureDevice, error: &error) if (error != nil) {
    // If any error occurs, simply log the description of it and don't continue any more.
    println("\(error?.localizedDescription)")
    return
} // Initialize the captureSession object.
captureSession = AVCaptureSession()
// Set the input device on the capture session.
captureSession?.addInput(input as AVCaptureInput)

一个 AVCaptureDevice对象代表了一个物理上的视频设备,在这里我们配置了一个默认的视频设备。由于我们将要捕获视频数据,所以我们调用 defaultDeviceWithMediaType方法和AVMediaTypeVideo来得到视频设备。我们以视频设备为输入参数去实例化了一个 AVCaptureSession会话,用它来实现实时视频捕获。AVCaptureSession会话是用来管理视频数据流从输入设备传送到输出端的会 话过程的。

在这里,这个会话的输出端被设定为一个AVCaptureMetaDataOutput对象,而这个 AVCaptureMetaDataOutput类是二维码读取的核心组成部分,它和 AVCaptureMetadataOutputObjectsDelegate协议一起,将被用来获取从输入设备传过来的元数据(就是摄像头捕获的二维 码)然后将它们翻译为人类可读的格式。如果你觉得这些听起来很奇怪或者你现在根本听不懂,不要担心,一会儿这些都会变得很清晰。现在要做的就是继续将下面 的代码加入viewDidLoad方法中去:

// Initialize a AVCaptureMetadataOutput object and set it as the output device to the capture session.
let captureMetadataOutput = AVCaptureMetadataOutput()
captureSession?.addOutput(captureMetadataOutput)

然后,接着把下面的代码也加进 去。在这里我们把self设置为captureMetadataOutput对象的代理。这就是为什么ORReaderViewController类要 实现AVCaptureMetadataOutputObjectsDelegate协议,当新的元数据对象被捕获到时,它们就被转发到这个代理的方法中 去。根据苹果的文档,这个队列必须是串行的,所以我们直接使用dispatch_get_main_queue()获取默认的GCD的串行执行队列。

// Set delegate and use the default dispatch queue to execute the call back
captureMetadataOutput.setMetadataObjectsDelegate(self, queue: dispatch_get_main_queue())
captureMetadataOutput.metadataObjectTypes = [AVMetadataObjectTypeQRCode]

metadataObjectTypes属性也非常重要,因为它的值会被用来判定整个应用程序对哪类元数据感兴趣。在这里我们将它指定为AVMetadataObjectTypeQRCode。

现 在我们完成了对AVCaptureMetadataOutput对象的设置,我们还需要在屏幕上显示摄像头捕获到的图像,这可以通过 AVCaptureVideoPreviewLayer(其实就是一个CALayer)来完成。然后使用这个预览图层和图像信息捕获会话来显示视频,这个 预览图层要作为当前视图的子图层添加进去,下面是相关代码:

// Initialize the video preview layer and add it as a sublayer to the viewPreview view's layer.
videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
videoPreviewLayer?.videoGravity = AVLayerVideoGravityResizeAspectFill
videoPreviewLayer?.frame = view.layer.bounds
view.layer.addSublayer(videoPreviewLayer)

我们终于能捕获视频了,这里要调用视频捕获回话的startRunning方法来启动它:

// Start video capture.
captureSession?.startRunning()

如果你编译运行这个应用,它应该在启动之后就开始捕获视频了。但是,等等,好像下面显示消息的label不见了。

可以添加如下代码来让它显示:

// Move the message label to the top view
view.bringSubviewToFront(messageLabel)

修改后重新运行程序,label上这是应该会显示“No QR code is detected”(没有检测到二维码)。

实现二维码读取

好了,现在这个应用看起来已经像一个视频捕获应用了,那么怎么样它才能扫描二维码并且翻译成有意义的明文呢?这个应用本身已经具备了检测二维码的能力,只是我们还不知道,这里是我们将要对应用做的改变:

1. 当检测到二维码时,应用会用一个绿色方框圈住二维码。
2. 这个二维码将被解码,然后将解码的信息显示在屏幕的下方。

初始化绿色方框

为了圈住二维码,我们首先创建一个UIView对象,并将它的边框设为绿色。在viewDidLoad方法中加入如下代码:

// Initialize QR Code Frame to highlight the QR code
qrCodeFrameView = UIView()
qrCodeFrameView?.layer.borderColor = UIColor.greenColor().CGColor
qrCodeFrameView?.layer.borderWidth = 2
view.addSubview(qrCodeFrameView!)
view.bringSubviewToFront(qrCodeFrameView!)

现在这个UIView是隐形的,因为它的尺寸默认会被设成零。之后,当检测到二维码时,我们再改变它的尺寸,那么它就会变成一个绿色的方框了。

解码二维码

像之前提到的,当AVCaptureMetadataOutput对象识别出来一个二维码,下边的方法(AVCaptureMetadataOutputObjectsDelegate的代理方法)就会被调用:

optional func captureOutput(_ captureOutput: 
AVCaptureOutput!,?   didOutputMetadataObjects metadataObjects: 
[AnyObject]!,? fromConnection connection: AVCaptureConnection!)

到现在为止我们还没有实现这个方法,这就是为什么我们不能翻译这个二维码。为了进一步捕获并且解码二维码,我们需要实现这个方法,对元数据对象进行进一步操作。下边是相关代码:

func captureOutput(captureOutput: AVCaptureOutput!, 
didOutputMetadataObjects metadataObjects: [AnyObject]!, fromConnection 
connection: AVCaptureConnection!) {
        
    // Check if the metadataObjects array is not nil and it contains at least one object.
    if metadataObjects == nil || metadataObjects.count == 0 {
        qrCodeFrameView?.frame = CGRectZero
        messageLabel.text = "No QR code is detected"
        return
    }
        
    // Get the metadata object.
    let metadataObj = metadataObjects[0] as AVMetadataMachineReadableCodeObject
        
    if metadataObj.type == AVMetadataObjectTypeQRCode {
        // If the found metadata is equal to the QR code metadata then update the status label's text and set the bounds
        let barCodeObject = 
videoPreviewLayer?.transformedMetadataObjectForMetadataObject(metadataObj
 as AVMetadataMachineReadableCodeObject) as 
AVMetadataMachineReadableCodeObject
        qrCodeFrameView?.frame = barCodeObject.bounds;
        
        if metadataObj.stringValue != nil {
            messageLabel.text = metadataObj.stringValue
        }
    }
}

这个方法的第二个参数(就是metadataObjects)是一个Array数组,它包含了所有已被读取的元数据对象。当然,首先 要做的就是要判断这个数组是否为空。如果为空,我们就要重置qrCodeFrameView的尺寸为零,并且把messageLabel的内容设为默认内 容。

如果数组里有元数据,我们就去判断它是否是二维码。如果是,我们接着就去找到二维码的边界。

这几行代码用来设置圈住二维 码的绿色方框。通过调用viewPreviewLayer的transformedMetadataObjectForMetadataObject方 法,元数据对象就会被转化成图层的坐标。通过这个坐标,我们可以获取二维码的边界并构建绿色方框。

let barCodeObject = 
videoPreviewLayer?.transformedMetadataObjectForMetadataObject(metadataObj
 as AVMetadataMachineReadableCodeObject) as 
AVMetadataMachineReadableCodeObject qrCodeFrameView?.frame = barCodeObject.bounds

最后,我们对二维码进行解码,得到人类可读信息。解码信息可以用过访问AVMetadataMachineReadableCodeObject 对象的stringValue属性得到,非常简单。

现在,一切准备就绪,点击Run按钮来编译并在真实设备上运行这个应用。软件打开后,对着一个二维码,这个应用就会马上检测到并且完成解码。

提示:你也可以通过http://www.qrcode-monkey.com来生成你自己的二维码,非常简单。

总结

现 在,通过使用AVFoundation框架去创建了一个二维码扫描应用变得前所未有的简单。另外除了二维码,这个框架还支持很多别的条码类别,例如 Code39,Code128,Aztec,和PDF417。大家可以尝试修改这个Xcode工程来实现这些类型的条码扫描。

这里你可以下载本文所述的完整的工程代码,仅供参考。
(本文为CocoaChina组织翻译,本译文权利归译者所有,未经允许禁止转载。)

用Swift开发二维码扫描器教程的更多相关文章

  1. iOS开发-二维码扫描和应用跳转

    iOS开发-二维码扫描和应用跳转   序言 前面我们已经调到过怎么制作二维码,在我们能够生成二维码之后,如何对二维码进行扫描呢? 在iOS7之前,大部分应用中使用的二维码扫描是第三方的扫描框架,例如Z ...

  2. Android开发--二维码开发应用(转载!)

    android项目开发 二维码扫描   基于android平台的二维码扫描项目,可以查看结果并且链接网址 工具/原料 zxing eclipse 方法/步骤   首先需要用到google提供的zxin ...

  3. 运行Google 官方zxing二维码扫描器

    首先,要去下载Zxing的源码,由于Zxing 的服务内容比较广,我们先把所有的源码都下载下来,使用的时候根据需要加载. 或者从开源中国下载https://www.oschina.net/questi ...

  4. iOS Swift WisdomScanKit二维码扫码SDK,自定义全屏拍照SDK,系统相册图片浏览,编辑SDK

    iOS Swift WisdomScanKit 是一款强大的集二维码扫码,自定义全屏拍照,系统相册图片编辑多选和系统相册图片浏览功能于一身的 Framework SDK [1]前言:    今天给大家 ...

  5. ipad开发:二维码扫描,摄像头旋转角度问题解决办法

    之前一直是在手机上开发,用系统原生二维码扫描功能,一点问题都没有,但是在ipad上,用户是横屏操作的,虽然界面旋转了,是横屏的,但是摄像头里显示的依然是竖屏效果,也就是说从摄像头里看到的和人眼看到的内 ...

  6. H5混合开发二维码扫描以及调用本地摄像头

    今天主管给了我个需求,说要用混合开发,用H5调用本地摄像头进行扫描二维码,我之前有做过原生安卓的二维码扫一扫,主要是通过调用zxing插件进行操作的,其中还弄了个闪光灯.但是纯H5的没接触过,心里没底 ...

  7. [二维码开发]二维码开发入门级demo

    最近开发一个项目,涉及到二维码开发,于是乎就到网上找下直接可用的资源,遇到两个问题: 1.网上资源不够完整,找到完整的资源,需要下载分,这个你知道的 2.ThoughtWorks.QRCode版本不对 ...

  8. Swift AVFoundation 二维码扫描和生成

    项目最终不须要支持iOS6了(泪崩),在二维码扫描这一块,可以全然的放弃ZXing库,改用系统的AVFoundation了,拿swift写了个Demo,效果例如以下: github地址:点这里 有关A ...

  9. C#中利用iTextSharp开发二维码防伪标签(1)

    开发的基本说明与尝试 一个亲戚朋友是做防伪码印刷的,之前的电话防伪.短信防伪都用Delphi给他设计,使用也挺不错,后来又加了一个基于asp的网页版防伪查询.由于业务需求,今年年初朋友又提成希望能够完 ...

随机推荐

  1. 使用gson(一)

    1.数组和json的转换 package com.test.gson; import com.google.gson.Gson; public class ArrayToJson { public s ...

  2. 读书笔记:php_tizag_tutorial

    昨天在实验室花了一天时间看了英文版的php_tizag_tutorial,因为上学期用php和bootstrap写过一个租房网站,对php还是比较熟悉.现在总结一下php_tizag_tutorial ...

  3. Linux C++ 开发简介(包括Linux守护线程)

    阅读目录 简介 操作系统 编辑器 编译器 构建系统 调试 IDE 可执行程序.动态库.静态库 服务 Windows服务简介 创建Windows服务 注册Windows服务 管理Windows服务 Li ...

  4. 更好地认知Azure

    编辑人员注释:本文章由 Windows Azure 网站团队项目经理 Erez Benari 撰写. 我们的网站 http://www.windowsazure.com 包含大量信息,并且也在不断添加 ...

  5. 在Web Api中快速实现JSonp

    本文翻译自:http://www.codeproject.com/Tips/631685/JSONP-in-ASP-NET-Web-API-Quick-Get-Started Concept: 同源策 ...

  6. OC中线程的状态相关

    1.线程的状态NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil]; ...

  7. lua语法 - 基础篇

    1. 注释 单行注释:--,类似于C++的// 多行注释:--[[ ... ]],类似于C++的/*...*/ 2. 语句 分隔符:分号或者空格,一般多行写一起,建议用分号 语句块:do ... en ...

  8. 前端面试题整理(js)

    1.HTTP协议的状态消息都有哪些? HTTP状态码是什么: Web服务器用来告诉客户端,发生了什么事. 状态码分类: 1**:信息提示.请求收到,继续处理2**:成功.操作成功收到,分析.接受3** ...

  9. How to find variable is empty in shell script

    (1). var="" if [ -n "$var" ]; then     echo "not empty" else     echo ...

  10. oncreate 测量尺寸

    在android中,在oncreate里面只是将布局信息设置好,并没有进行布局,因此是没法进行测量view或者屏幕的长高,可以通过下面的observer来观察,当view布局完成之后会回调下面的两个接 ...