iOS8采用系统方法采集和生成二维码

项目中要添加扫码功能。虽然网上已经有很多Demo,但还是想自己好好看看系统文档,亲自踩下坑。将学习过程记录下。
Demo已经放到GitHub上了。主要有以下功能特点

  1. 支持iOS8.0以上系统;
  2. 采用系统API扫描识别二维码;
  3. 采用系统API识别相册中的二维码;若识别失败,调用ZBar方法再识别一次;
  4. 采用系统API生成二维码,二维码中间可以添加一个小图片,可生成彩色二维码;
  5. 扫码界面布局仿照微信。
  6. 可打开手电筒、切换前后置相机

效果图

效果图

概述

采用AVFoundataion框架来进行视频采集。至少需要以下几步

  • 一个AVCaptureDevice的实例,用来描述输入设备,比如相机或者麦克风
  • 一个AVCaptureInput子类的实例,用来从输入设备配置端口。此处使用的是子类AVCaptureDeviceInput
  • 一个AVCaptureOutput子类的实例,用来管理输出数据。此处使用的是子类AVCaptureMetadataOutput
  • 一个AVCaptureSession的实例,用来协调输入输出流

如果要向用户展示正在录制的内容,可以使用AVCaptureVideoPreviewLayer(CALayer的子类)的实例

Capture Session

AVCaptureSession这个类是管理采集数据的中枢。我们可以使用这个类去管理从输入到输出的数据流。添加想要的输入输出数据到这个session,然后调用startRunning开始采集数据,调用stopRunning停止数据流。

AVCaptureSession *session = [[AVCaptureSession alloc] init];
// Add inputs and outputs.
[session startRunning];

startRunning会同步执行并阻塞当前线程,直到AVCaptureSession启动或者启动失败。如果启动失败,我们会接收到一个通知AVCaptureSessionRuntimeErrorNotification
注意:由于startRunning执行需要花费一段时间,并且会阻塞当前线程,因此我们应该在一个串行子线程中去执行这个方法,这样可以避免主线程UI被锁。
参考例子AVCam-iOS: Using AVFoundation to Capture Images and Movies

配置Session

设置图片的质量和分辨率。系统提供以下几种配置。当需要识别的图片比较小时,就选高分辨率,反之则选择低分辨率。一般来说选择AVCaptureSessionPreset1280x720或者AVCaptureSessionPreset640x480就可以了。

image1

在设置之前,先要判断系统是否支持这个分辨率

if ([session canSetSessionPreset:AVCaptureSessionPreset1280x720]) {
    session.sessionPreset = AVCaptureSessionPreset1280x720;
}
else {
    // Handle the failure.
}

如果需要根据实际情况选择不同的分辨率,可以使用 beginConfiguration , commitConfiguration这两个方法

[session beginConfiguration];
// Remove an existing capture device.
// Add a new capture device.
// Reset the preset.
[session commitConfiguration];

监听CaptureSession状态

session开始或者停止运行,发生中断等,都会发出一个通知。
系统提供了如下几种通知,可以监听并且做一些异常判断。

AVCaptureSessionRuntimeErrorNotification
AVCaptureSessionDidStartRunningNotification
AVCaptureSessionDidStopRunningNotification
AVCaptureSessionWasInterruptedNotification
AVCaptureSessionInterruptionEndedNotification

添加输入设备

NSError *error;
AVCaptureDeviceInput *captureDeviceInput =
        [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
if (!captureDeviceInput) {
    // Handle the error appropriately.
}

AVCaptureSession *captureSession = <#Get a capture session#>;
if ([captureSession canAddInput:captureDeviceInput]) {
    [captureSession addInput:captureDeviceInput];
}
else {
    // Handle the failure.
}

获取输出数据

AVCaptureSession *captureSession = <#Get a capture session#>;
AVCaptureMetadataOutput *outPut =  [[AVCaptureMetadataOutput alloc] init];
if ([captureSession canAddOutput:outPut]) {
    [captureSession addOutput:outPut];
    //设置输出执行的委托以及委托运行的线程
    [outPut setMetadataObjectsDelegate:self queue:_metadataObjectsQueue];
//设置支持的类型,此处设置为支持所有的类型
    outPut.metadataObjectTypes = [self.videoDeviceOutput availableMetadataObjectTypes];
}
else {
    // Handle the failure.
}

输出代理方法

- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection
{
    for (AVMetadataObject *object in metadataObjects) {
          AVMetadataMachineReadableCodeObject *codeObj = (AVMetadataMachineReadableCodeObject *)object;
          NSString *QRMessage = codeObj.stringValue;
        }
}

如果能够正确识别二维码,那么QRMessage就是识别出的数据
可参考例子AVCamBarcode: Using AVFoundation to Detect Barcodes and Faces
通过api的说明可知,当采集到数据后,可在此方法中获得输出数据。此委托方法会运行在上面设置代理时指定的线程中。由于这个方法会很频繁的调用,因此我们必须注意性能问题。

至此,调用相机扫描二维码的功能就完成了。下面看一下如何识别相册中的二维码。

识别图片中的二维码

使用了CIDetector类来识别图片中的二维码。除了二维码,它还可以识别人脸等其他类型。系统提供了几种类型:

CIDetectorTypeFace
CIDetectorTypeRectangle
CIDetectorTypeQRCode

识别的代码如下

- (NSString *)decodeQRImage:(UIImage*)image
{
    CIContext *context = [CIContext contextWithOptions:nil];
    CIDetector *detector = [CIDetector detectorOfType:CIDetectorTypeQRCode context:context options:@{CIDetectorAccuracy:CIDetectorAccuracyHigh}];
    CIImage *ciImage = [CIImage imageWithCGImage:image.CGImage];
    NSArray *features = [detector featuresInImage:ciImage];
    
    //如果使用系统发发不能识别图中二维码,再采用ZBar尝试一次
    if (features.count > 0) {
        CIQRCodeFeature *feature = [features firstObject];
        return feature.messageString;
    }else {
        //采用ZBar识别
        CGImageRef cgImageRef = image.CGImage;
        
        ZBarQRDecoder *QRDecoder = [[ZBarQRDecoder alloc] init];
        NSArray *symbols = [QRDecoder scanImage:cgImageRef];

        for (ZBarSymbol *symbol in symbols) {
            return symbol.data;
        }
    }
    return nil;
}

注意 NSArray *features = [detector featuresInImage:ciImage];这个方法不稳定,在某些设备上会存在识别失败的问题。看网上有人说在iPhone5、iPhone4等老设备上会存在识别不出来的问题。我自己亲测的是,使用5c拍摄的二维码全都识别不了。这个应该是苹果API的问题。为了解决这个问题,引用了ZBar,当使用系统API识别失败时,用ZBar再尝试一次。

ZBarSDK

ZBarSDK
在github上看到ZBar最近的更新也是4年前了。虽然已经这么久不维护了,但是既然当年这份代码能运行的很好,现在二维码的标准也没有什么更新,那这份代码依然能够很好的运行。完全可以满足我目前的需求。
从GitHub上下载的ZBar工程有很多问题,运行起来也会有很多错误。因为我的项目中只需要使用识别图片的功能,因此我将这个库整理了一下,删掉了不必要的操作。具体也是看我的Demo。
需要注意的两点是

  1. 需要在工程中添加libiconv.2.4.0.tbd链接库
  2. ZBarSymbol.m, ZBarImageScanner.m, ZBarImage.m三个文件要禁止使用ARC,Build Phases中添加-fno-objc-arc
  3. 在工程中导入头文件 ZBarSDK.h,调用ZBarQRDecoder类的scanImage方法来扫描图片。

生成二维码

使用CIFilter类来创建自定义的二维码滤镜,生成二维码图片。
关于自定义滤镜的使用,可以参考官方文档
使用CIQRCodeGenerator创建一个二维码滤镜

- (void)generateQRCode
{
    // Create a new filter with the given name.
    CIFilter *filter = [CIFilter filterWithName:@"CIQRCodeGenerator"];
    if (!filter) return;
    
    // -setDefaults instructs the filter to configure its parameters
    // with their specified default values.
    [filter setDefaults];
    
    //二维码数据
    NSString *QRMessage = @"http://www.jianshu.com/users/3688d37243de/latest_articles";
    //    官方建议使用 NSISOLatin1StringEncoding 来编码,但经测试这种编码对中文或表情无法生成,改用 NSUTF8StringEncoding 就可以了。
    NSData *inputData = [QRMessage dataUsingEncoding:NSUTF8StringEncoding];
    
    
    // 设置过滤器的输入值, KVC赋值
    [filter setValue:inputData forKey:@"inputMessage"];
    CIImage *outputImage = [filter outputImage];
    
    // 输出的图片比较小,需要放大。此处放大20倍
    outputImage = [outputImage imageByApplyingTransform:CGAffineTransformMakeScale(20, 20)];
    
    UIImage *QRImage = [UIImage imageWithCIImage:outputImage];
    _QRCodeImageView.image = QRImage;
}

为二维码添加中间的小图片

此处直接在二维码的中心画一个UIImageView

-(void)drawSmallIcon
{
    UIImageView *iconView = [[UIImageView alloc]init];
    iconView.layer.cornerRadius = 4.0f;
    iconView.layer.borderColor = [UIColor whiteColor].CGColor;
    iconView.layer.borderWidth = 2.0f;
    [_QRCodeImageView addSubview:iconView];
    
    UIImage *avator = [UIImage imageNamed:@"avator.jpg"];
    avator = [avator resizeToSize:CGSizeMake(100, 100)];
    iconView.image = avator;
    //设置小图片为二维码的20%
    [iconView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.center.equalTo(0);
        make.width.equalTo(_QRCodeImageView.mas_width).multipliedBy(0.2f);
        make.height.equalTo(iconView.mas_width);
    }];
}

除了文中指出的文档和demo外,还参考了以下文章

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

推荐阅读更多精彩内容