使用原生 API(AVCaptureSession)实现二维码扫描

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];

        });

这样,当我们在延时后重新开启会话并打开扫描,我们所要的扫描功能就又回来了~~

你可能感兴趣的:(使用原生 API(AVCaptureSession)实现二维码扫描)