AVCaptureSession
是iOS 原生用于捕捉视频和音频,协调音视频输入输出流的核心类,它包含于强大的 AVFoundation
框架当中.此处我们只讨论使用 AVCaptureSession
实现二维码扫描的功能.
要实现二维码扫描首先就要获取相机,使用AVCaptureDevice
判断相机是否可用:
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
if (device==nil) {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"提示" message:@"设备没有摄像头" preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"确认" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
}]];
[self presentViewController:alert animated:YES completion:nil];
return;
}
相机可用时,还要判断是否有使用权限:
AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
如果没有,可以做一些提示等,不多做表述.
当我们获取了相机使用权限时,就可以开始下面的处理了,首先要初始化输入/输出流
_input = [AVCaptureDeviceInput deviceInputWithDevice:self.device error:nil];
_output = [[AVCaptureMetadataOutput alloc]init];
还要设置代理:
//设置代理 在主线程里刷新
[_output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
之后我们还要设置好扫描区域:
CGFloat top = TOP/SCREEN_HEIGHT;
CGFloat left = LEFT/SCREEN_WIDTH;
CGFloat width = 220/SCREEN_WIDTH;
CGFloat height = 220/SCREEN_HEIGHT;
[_output setRectOfInterest:CGRectMake(top,left, height, width)];
之后初始化 AVCaptureSession 对象,并将输入/输出添加进会话对象:
_session = [[AVCaptureSession alloc]init];
[_session setSessionPreset:AVCaptureSessionPresetHigh];
//1.判断输入能否添加到会话中
if ([_session canAddInput:self.input])
{
[_session addInput:self.input];
}
// 2.判断输出能够添加到会话中
if ([_session canAddOutput:self.output])
{
[_session addOutput:self.output];
}
然后要将扫描到的信息进行解析出来:
//注意点: 设置数据类型一定要在输出对象添加到会话之后才能设置
//设置扫码支持的编码格式(如下设置条形码和二维码兼容)
_output.metadataObjectTypes=@[AVMetadataObjectTypeQRCode,AVMetadataObjectTypeEAN13Code, AVMetadataObjectTypeEAN8Code, AVMetadataObjectTypeCode128Code];
最后还要添加一个预览图层(我们当然不希望打开相机之后一片空白,什么都看不到~~)
// 添加预览图层,传递_session是为了告诉图层将来显示什么内容
_previewLayer =[AVCaptureVideoPreviewLayer layerWithSession:_session];
_previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
_previewLayer.frame = self.layerView.layer.bounds;
[self.layerView.layer insertSublayer:_previewLayer atIndex:0];
最后的最后,当然就是启动扫描咯
[_session startRunning];
很重要的一点,当我们扫描完成以后,我们总会做一些操作,比如说做跳转或者做弹窗等等,我们上面已经设置了输出流的代理,那么这些操作就完全可以放到代理里面去做了:
-(void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection{
if ([metadataObjects count] >0)
{
//停止扫描
[_session stopRunning];
AVMetadataMachineReadableCodeObject * metadataObject = [metadataObjects objectAtIndex:0];
NSString *stringValue = metadataObject.stringValue;
NSLog(@"扫描结果:%@",stringValue);
} else {
[LSAlertTools presentAlertViewWithViewController:self title:nil message:@"扫描信息有误" duration:.7];
}
}
按照上面的进行操作,我们会发现当完成扫描时,扫描界面已经被冻结了,这是因为相机扫描的是当时捕捉到的画面,说白了就是相机将当时捕捉到的画面截图,并且定格在了那里.那么我们要怎么解决这个令人尴尬的问题呢?
按照上面的方法,我们在代理当中调用了[_session stopRunning]
来停止扫描(注意:这是一定要做的,否则扫描会一直进行,代理也会一直被调用),这是没问题的,但是我们还要断开AVCaptureSession 的会话连接,如果不断开造成的情况就是画面定格.
//断开会话连接
[[self.previewLayer connection] setEnabled:NO];
但是我们已经停止了扫描,又断开了会话,这样势必造成如果不销毁扫描界面,重新初始化,我们就无法重启扫描功能,我这里做了一个延时处理:
[_session stopRunning];
[[self.previewLayer connection] setEnabled:NO];
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW,
(int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[[self.previewLayer connection] setEnabled:YES];
[_session startRunning];
});
这样,当我们在延时后重新开启会话并打开扫描,我们所要的扫描功能就又回来了~~