iOS-AVCaptureSession扫描读取二维码

二维码简单介绍

二维码(Quick Response Code,QRCode)是一种由水平和垂直两个方向上的线条设计而成的二维条形码,能够存储数据信息,本文主要是介绍二维码的读取(不涉及二维码的生成)ios

二维码读取

介绍

读取二维码就是经过扫描二维码图像来获取其中的数据信息,任何条形码的扫描都是基于视频采集,所以须要用到AVFoundation框架objective-c

如下是AVFoundation库的概述数组

The AVFoundation framework combines six major technology areas that together encompass a wide range of tasks for capturing, processing, synthesizing, controlling, importing and exporting audiovisual media on Apple platforms.markdown

AVFoundation框架结合了六个主要技术领域,这些领域共同涵盖了在Apple平台上捕获,处理,合成,控制,导入和导出视听媒体的普遍任务。框架

对于二维码的读取,咱们主要用到该库中的Capture部分,即AVCaptureSession类,如下是其概述async

image.png 能够看到该类继承自NSObject,主要功能是用于管理capture(捕获)活动并协调从输入设备到捕获设备的数据流ide

扫描过程概述

扫描二维码的过程即从摄像头捕获二维码图像(input)到解析出字符串内容(output)的过程,该过程主要就是经过AVCaptureSession对象来实现oop

AVCaptureSession对象用于协调从输入到输出的数据流,在执行过程当中,须要先将输入和输出添加到该对象中,而后经过发送startRunningstopRunning消息来启动或中止数据流,最后经过AVCaptureVideoPreviewLayer对象来将捕获的视频显示在屏幕上动画

其中,输入对象一般是AVCaptureDeviceInput对象,经过AVCaptureDevice的实例来得到,输出对象一般是AVCaptureMetaDataOutput对象,该对象是读取二维码的核心部分,须要结合AVCaptureMetaDataOutputObjectsDelegate协议结合使用,能够捕获在输入设备中找到的任何元数据(metadata就是元数据的意思),并将其转换为字符串的格式ui

接下来咱们来结合代码详细说明每一个过程

具体步骤

1. 导入AVFoundation框架

#import <AVFoundation/AVFoundation.h>
复制代码

2. 判断权限

因为扫描二维码过程须要用到摄像头,所以咱们须要设置摄像头的权限并进行判断

第一步:设置权限

有两种方式设置权限

  1. 直接在info.plist文件中添加

image.png 再经过source code的方式打开,能够看到自动添加了两行代码

image.png

所以咱们其实也能够直接在源代码中添加对应的代码,就是下面的方法

  1. 经过source code的方式在info.plist文件中添加
<key>NSCameraUsageDescription</key>
<string>获取相机权限</string>
复制代码

image.png

触类旁通,对于其余的须要获取权限设置也能够经过如上两个方式实现,例如麦克风、地理位置等,如图

image.png

第二步:判断权限

代码简写了,核心的部分以下

#pragma mark --判断权限
-(void)judgeAuthority{
//判断权限的方法
    [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
        //要放到主线程中刷新
        dispatch_async(dispatch_get_main_queue(), ^{
            // 若已受权
            if (granted) {
                 //调用扫描二维码的方法
            } else {
                //若未受权,提示弹窗
                ....
            }
        });
    }];
}
复制代码

且其中最核心的部分就是requestAccessForMediaType:(AVMediaType)mediaType completionHandler:(void (^)(BOOL granted))handler ; 这个方法,用于请求权限,包含两个参数

  • 第一个参数AVMediaType是媒体类型

有以下几种类型

image.png

  • 第二个参数是一个block块,写相关判断的代码便可

关于提示弹窗,咱们用的是UIAlertController

  1. 建立UIAlertController对象
NSString *title = @"请在iPhone的“设置-隐私-相机“选项中,容许App访问你的相机";
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"提示" message:title preferredStyle:UIAlertControllerStyleAlert];
复制代码
  1. 建立UIAlertAction对象,即按钮
UIAlertAction *conform = [UIAlertAction actionWithTitle:@"确认" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
                    NSLog(@"点击了确认按钮");
                }];
                UIAlertAction *cancel = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
                    NSLog(@"点击了取消按钮");
                }];
复制代码
  1. 将按钮添加到alert中
[alert addAction:conform];
[alert addAction:cancel];
复制代码
  1. 显示弹窗
[self presentViewController:alert animated:YES completion:nil];
复制代码

显示效果以下,首先会系统自动弹窗请求相机权限

IMG_0006.PNG

若点击了不容许,即没法拿到相机权限,显示弹窗

IMG_0007.PNG

3. 建立AVCaptureSession对象

@property (nonatomic, strong) AVCaptureSession *captureSession;

_captureSession = [[AVCaptureSession alloc]init];
复制代码

4. 为AVCaptureSession对象添加输入输出

//1. 初始化设备
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];

//2. 建立输入,基于device实例的输入
AVCaptureDeviceInput *deviceInput = [AVCaptureDeviceInput deviceInputWithDevice:device error:nil];

//3. 建立输出
AVCaptureMetadataOutput *metadataOutput = [[AVCaptureMetadataOutput alloc] init];

//4. 添加输入输出
[_captureSession addInput:deviceInput];
[_captureSession addOutput:metadataOutput];
复制代码

5. 配置AVCaptureMetaDataOutput对象

首先是设置代理,而后是设置元数据类型

//1. 设置代理
[metadataOutput setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];

//2. 设置元数据类型,由于这里是二维码的扫描,因此数据类型是AVMetadataObjectTypeQRCode,注意是须要传入数组
[metadataOutput setMetadataObjectTypes:@[AVMetadataObjectTypeQRCode]];
复制代码

6. 建立并设置AVCaptureVideoPreviewLayer对象来显示捕获到的视频

@property (nonatomic, strong) AVCaptureVideoPreviewLayer *videoPreviewLayer;//展现layer

//1. 实例化预览涂层图层
_videoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:_captureSession];

//2. 设置预览图层填充方式
[_videoPreviewLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];

//3. 设置图层的frame
[_videoPreviewLayer setFrame:_viewPreview.layer.bounds];

//4. 将图层添加到预览view的图层上
[_viewPreview.layer addSublayer:_videoPreviewLayer];

//5. 设置扫描范围,这里使用的是相对位置
metadataOutput.rectOfInterest = CGRectMake(0.2f, 0.2f, 0.8f, 0.8f);
复制代码

这里用到了rectOfInterest这个属性,是用于设置元数据的搜索区域的,肯定矩形,矩形的坐标原点位于左上角

7. 实现代理方法

#pragma mark --AVCaptureMetadataOutputObjectsDelegate
- (void)captureOutput:(AVCaptureOutput *)output didOutputMetadataObjects:(NSArray<__kindof AVMetadataObject *> *)metadataObjects fromConnection:(AVCaptureConnection *)connection{
    //判断是否正在读取数据
    if (!_isReading) {
        //没有读取,返回
        return;
    }
    //若metadataObjects.count > 0,表明扫描到二维码
    if (metadataObjects.count > 0) {
        _isReading = NO;
        //
        AVMetadataMachineReadableCodeObject *metadataObject = metadataObjects[0];
        NSString *result = metadataObject.stringValue;
        if (self.resultBlock) {
            self.resultBlock(result);
        }
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [self.navigationController popViewControllerAnimated:YES];
    }
}
复制代码

开发过程当中遇到的问题和解决方法

其实上面也有一些已经说了例如权限设置这些,如下还有几个点

1. 程序运行黑屏

AppDelegate.h中添加UIWindow属性

@property (nonatomic, strong)UIWindow *window;
复制代码

2. 导航栏不显示title

title属性是从UIViewController上面继承过来的,而不是UINavigationController上面的名字

因为UINavigationController属于容器,因此最少须要一个RootVIewController

而后在RootViewController的viewDidLoad设置title而不是在UINavigationController的subclass中设置

self.navigationController.title = @"扫一扫";  //本来的代码
self.title = @"扫一扫"; //修改成self便可
复制代码

3. 按钮不居中

//本来的代码,发现按钮水平方向偏右,竖直方向”居中“
btn.frame = CGRectMake(self.view.bounds.size.width / 2.0, self.view.bounds.size.height / 2.0, 80, 40);
//解决方法,水平方向 - 40 便可(尚未纠结缘由)
btn.frame = CGRectMake(self.view.bounds.size.width / 2.0 - 40 , self.view.bounds.size.height / 2.0, 80, 40);
复制代码

但其实有更好的方法就是直接设置center 便可

//先肯定frame,定长度和宽度
btn.frame = CGRectMake(0,0, 80, 40);
//肯定中心,即坐标
btn.center = self.view.center;
复制代码

4. block属性传值

简单梳理block属性传值的大体代码

//ViewController.m

//点击按钮时调用jumpToScanVC方法
[btn addTarget:self action:@selector(jumpToScanVC) forControlEvents:UIControlEventTouchUpInside];

-(void)jumpToScanVC{
    SecondViewController *secondVC = [[SecondViewController alloc]init];
    secondVC.secondBlock = ^(NSString * _Nonnull string) {
        self.label.text = string;
    };
    [self.navigationController pushViewController:secondVC animated:NO];
}
复制代码
// SecondViewController.h

//定义block属性
@interface SecondViewController : UIViewController
@property (nonatomic, copy) void(^secondBlock)(NSString *string);
@end
复制代码
// SecondViewController.m

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    if (_secondBlock) {
        _secondBlock(@"hahaha");
    }
    [self.navigationController popViewControllerAnimated:NO];
}
复制代码

能够看到在第二个控制器中调用了block并将值传递到了第一个控制器

5. UIAlertController的用法

扫描结果显示并弹窗,可是控制器没有被成功pop出去,报错显示

popViewControllerAnimated: called on <UINavigationController 0x101827e00> while an existing transition or presentation is occurring; the navigation stack will not be updated.
复制代码

其实就是咱们UIAlertController的弹窗动画和pop控制器的动画冲突了

但其实咱们的navigation stack已经pop掉了,只是没法显示动画

所以咱们能够经过延迟执行来达到先完成弹窗动画,再完成pop动画

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [self.navigationController popViewControllerAnimated:YES];
        });
复制代码

6. 点击扫描后跳转了页面可是没有显示扫描框,控制器卡死

缘由是没有放到主线程中去刷新UI致使了卡死

image.png

7. 防止NSTimer致使没法释放的问题

由于设置了扫描线(其实就是一个装饰,显得专业一点),扫描线的移动用到了计时器,可是注意计时器的使用极可能致使内存没法释放的状况,因此咱们要事先将NSTimer对象置空

#pragma mark --结束
-(void)stopRunning{
    //判判定时器是否正在工做,若还在工做另起暂停并置空
    if ([_timer isValid]) {
        //正在工做就使其失效
        [_timer invalidate];
        //并给定时器赋值nil
        _timer = nil;
    }
    [self.captureSession stopRunning];
    ...
}
复制代码

运行界面演示

二维码图片

%E4%BA%8C%E7%BB%B4%E7%A0%81%E5%9B%BE%E7%89%87_5%E6%9C%8817%E6%97%A514%E6%97%B657%E5%88%8612%E7%A7%92.png

IMG_0008.PNG

IMG_0009.PNG

IMG_0010.PNG


参考博客

ios使用AVFoundation读取二维码的方法

iOS利用AVCaptureSession实现二维码扫描

相关文章
相关标签/搜索