AVFoundation 实现二维码扫描

项目中有个地方需要实现扫二维码扫描的功能。在iOS 7以后,AVFoundation这个framework已经提供了相机扫描识别二维码的功能,所以就自己研究一下。

0x01

首先,想要获取用户的相机权限的话,需要在Info.plist中添加NSCameraUsageDescription这个key,value的值用于展示给用户告之使用相机的用途。如果没有添加上这个key的话,就会出现下面这样的错误:

[access] This app has crashed because it attempted to access privacy-sensitive data without a usage description.  The app's Info.plist must contain an NSCameraUsageDescription key with a string value explaining to the user how the app uses this data.
复制代码

0x02 设定扫描范围的两种方法

要设定扫描范围的话,可以通过设置captureMetadataOutputrectOfInterest

因为AVCapture的坐标系是横屏的,所以其坐标系跟window的坐标系是x轴和y轴相反的。所以在设置的时候需要翻转坐标系,其中一种方法就是自己交换x轴和y轴的值。另外有一点需要注意的是**rectOfInterest**的类型是CGRect,这个需要区别于我们平常使用的CGRect,因为rectOfInterest的值是比例值。

第一种复制方式:

CGFloat width = self.view.frame.size.width * 3/5;
    CGFloat height = width;
    CGFloat x = self.view.frame.size.width / 5;
    CGFloat y = (self.view.frame.size.height - height) / 2;
    CGRect frame = CGRectMake(x, y, width, height);    captureMetadataOutput.rectOfInterest = CGRectMake(frame.origin.y / self.view.frame.size.height,
                                                      frame.origin.x / self.view.frame.size.width,
                                                      frame.size.height / self.view.frame.size.height,
                                                      frame.size.width / self.view.frame.size.width);

复制代码

从上面可以看到,原本应该为x值的地方变成了y,y变成了x,交换了x轴和y轴的值。所有值都是比例值。

第二种赋值方式:

CGFloat width = self.view.frame.size.width * 3/5;
        CGFloat height = width;
        CGFloat x = self.view.frame.size.width / 5;
        CGFloat y = (self.view.frame.size.height - height) / 2;
        CGRect frame = CGRectMake(x, y, width, height);
        self.captureMetadataOutput.rectOfInterest = [self.previewLayer metadataOutputRectOfInterestForRect:frame];
复制代码

这一种方式的话,调用了AVCaptureVideoPreviewLayermetadataOutputRectOfInterestForRect:方法。该方法直接将previewLayer的坐标系转换为output的坐标系,也就省去了我们手动转换的步骤。

**第二种方式需要在[session startRunning];之后才执行。**如果在running之前进行调用metadataOutputRectOfInterestForRect:的话,会出现GAffineTransformInvert: singular matrix.这样的错误。谷歌了一下,说是坐标系为CGRectZero.所以猜测在running之前,previewLayer并未执行,所以转换出来的结果出现了error。

0x03 一般流程

  1. 获取输入设备
// Find the input for default capture device
    AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
    NSError *error = nil;
    AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
    if (!input) {
        NSLog(@"Failed to init capture input: %@", [error localizedDescription]);
        return;
    }
复制代码
  1. 添加输出
// AVCaptureMetadataOutput对象用于由相关连接发出的元数据对象并将其转发到delegate进行处理
self.captureMetadataOutput = [[AVCaptureMetadataOutput alloc] init]; 
复制代码
  1. 初始化session对象并设置输入输出
self.captureSession = [[AVCaptureSession alloc] init];[self.captureSession addInput:input];
[self.captureSession addOutput:self.captureMetadataOutput];
复制代码
  1. 设置metadata输出
dispatch_queue_t dispatchQueue;
    dispatchQueue = dispatch_queue_create("Scanner Queue", nil);
    [self.captureMetadataOutput setMetadataObjectsDelegate:self queue:dispatchQueue];
    [self.captureMetadataOutput setMetadataObjectTypes:@[AVMetadataObjectTypeQRCode]] // 设置扫描类型
复制代码
  1. 添加预览层
self.previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.captureSession];
    self.previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
    self.previewLayer.frame = self.view.bounds;
复制代码
  1. 开启扫描
[self.captureSession startRunning];
复制代码
  1. 实现AVCaptureMetadataOutputObjectsDelegate方法
#pragma mark - AVCaptureMetadataOutputObjectsDelegate
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection {
    NSLog(@"%@", metadataObjects);
}
复制代码

一般步骤就是这样,基本就能完成AVFoundation的扫描二维码操作。

但是在运行过程中发现,AVCaptureSessionstartRunning方法在调用的时候会稍微的卡顿。在Stack Overflow中看到的答案是

因为该方法会阻塞主线程,所以利用GCD的异步来执行相关的方法。我自己的代码如下:

dispatch_async(dispatchQueue, ^{
        [self.captureSession startRunning];
        dispatch_async(dispatch_get_main_queue(), ^{
            [self.view.layer addSublayer:self.previewLayer];
            self.captureMetadataOutput.rectOfInterest = [self.previewLayer metadataOutputRectOfInterestForRect:self.scanCrop];
            self.maskView = [[ScannerMaskView alloc] initWithFrame:self.view.bounds cropFrame:self.scanCrop];
            [self.view addSubview:self.maskView];

            [self.view bringSubviewToFront:self.topBarView];
            [self.indicatorView removeFromSuperview];
        });
    });
复制代码

开始running的同时,主线程开始更新相关的UI界面。目前还没发现其他问题。望指教.

Github上上传了一个Demo: QrcodeScanner


  1. iOS AVFoundation and Multithreading
  2. iOS开发-QRCode-二维码识别与生成
  3. Scanning Barcodes and QR Codes with AVFoundation on iOS7+
  4. How To Scan QR Code Using AVFoundation Framework

转载于:https://juejin.im/post/5a30e0c1f265da43062ac272

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值