美文网首页
OC实现二维码扫描

OC实现二维码扫描

作者: 大鹏鸟 | 来源:发表于2018-03-16 17:14 被阅读202次

对于写代码,我一向是只要时间允许,能自己写的绝对不用第三方的。因为总是用第三方的话,会给工程带来很大的不稳定性和很多坑。嗯,我现在就身在其中。
二维码扫描,这是一个很普遍的功能,在我负责的app里也有,只不过那是个第三方的。乘着刚刚用Swift搭完架子,现在决定尝试对其封装一下,然后应用到工程中。
二维码扫描使用的是AVFoundation框架 ,主要涉及的类有:

  • AVCaptureDevice
  • AVCaptureDeviceInput
  • AVCaptureMetadataOutput
  • AVCaptureVideoPreviewLayer
  • AVCaptureSession
    要说应用,肯定很简单,只不过这里面的坑太多太多了。

简单的流程,总结如下:

  • 1.使用方法[AVCaptureDevice authorizationStatusForMediaType:mediaType];检查权限,但是这里的type类型只能是AVMediaTypeVideo或者AVMediaTypeAudio
  • 2.使用方法+ (void)requestAccessForMediaType:(AVMediaType)mediaType completionHandler:(void (^)(BOOL granted))handler获取权限,这里需要注意的是回调不一定就是在主线程
  • 3.创建device对象
  • 4.使用device对象创建AVCaptureDeviceInput对象
  • 5.初始化AVCaptureMetadataOutput对象和设置代理
  • 6.初始化AVCaptureSession任务管理对象
  • 7.使用如下代码添加输入输出:
if([session canAddInput:deviceInput]) {
        [session addInput:deviceInput];
    }
    if([session canAddOutput:output]) {
        [session addOutput:output];
    }
  • 8.设置output对象的属性metadataObjectTypes值为AVMetadataObjectTypeQRCode
  • 9.使用session初始化AVCaptureVideoPreviewLayer,该layer就是背景透明的地方
  • 10.使用session调用方法startRunning启动扫描
  • 11.调用layer的- (CGRect)metadataOutputRectOfInterestForRect:(CGRect)rectInLayerCoordinates方法获取output的rectOfInterest,这里的rectInLayerCoordinates参数就是扫描框的位置,即识别区。注意,这里必须要写到startRunning调用之后。
  • 12.调用layer的rectForMetadataOutputRectOfInterest方法获取扫描框的位置
  • 13.获取结果

以上,这是一个比较好理解和使用的思路,这里的rectOfInterest其实也可以自己计算获取到,但是比较难掌握,因为它和AVCaptureVideoPreviewLayer的videoGravity有关,也和session的sessionPreset有关,具体的待我稍作研究补上。
具体的代码如下:

- (void)configUI {
    
    AVMediaType mediaType = AVMediaTypeVideo;
    // 先检查权限,如果没有权限,就去申请获取
    AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:mediaType];
    switch (status) {
        case AVAuthorizationStatusNotDetermined:{
            [AVCaptureDevice requestAccessForMediaType:mediaType completionHandler:^(BOOL granted) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    if (granted) {
                        [self continueConfigUI:mediaType];
                    } else {
                        NSLog(@"denied");
                    }
                });
            }];
        }
            break;
        case AVAuthorizationStatusAuthorized:{
            [self continueConfigUI:mediaType];
        }
            break;
            
        default:
            NSLog(@"denied");
            break;
    }
}

- (void)continueConfigUI:(AVMediaType)mediaType {
    AVCaptureDevice * device = [AVCaptureDevice defaultDeviceWithMediaType:mediaType];
    if (!device) {
        return;
    }

    CGRect cameraFrame = CGRectMake(10, 100, 300, 300);
    CGRect scanRect = CGRectMake(cameraFrame.size.width / 2 - 100, cameraFrame.size.height / 2 - 100, 200, 200);
    
    AVCaptureDeviceInput * deviceInput = [AVCaptureDeviceInput deviceInputWithDevice:device error:nil];
    output = [[AVCaptureMetadataOutput alloc] init];
    [output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
    
//    CGFloat top = scanRect.origin.y / CGRectGetHeight(cameraFrame);
//    CGFloat left = scanRect.origin.x / CGRectGetWidth(cameraFrame);
//    CGFloat width = scanRect.size.width / CGRectGetWidth(cameraFrame);
//    CGFloat height = scanRect.size.height / CGRectGetHeight(cameraFrame);
//    output.rectOfInterest = CGRectMake(top, left, height, width);
    
    session = [[AVCaptureSession alloc] init];
    if([session canAddInput:deviceInput]) {
        [session addInput:deviceInput];
    }
    if([session canAddOutput:output]) {
        [session addOutput:output];
    }
    output.metadataObjectTypes = @[AVMetadataObjectTypeQRCode]; // 必须放在上面设置完session之后
    
    _cameraView = [[UIView alloc] initWithFrame:cameraFrame];
    _cameraView.backgroundColor = [UIColor whiteColor];
    [self.view addSubview:_cameraView];
    
    // 添加扫描画面
    AVCaptureVideoPreviewLayer * layer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:session];
    layer.videoGravity = AVLayerVideoGravityResizeAspectFill; // 该值会影响展示
    layer.frame = _cameraView.bounds;
    [_cameraView.layer addSublayer:layer];
//     output.rectOfInterest = [layer metadataOutputRectOfInterestForRect:scanRect];
    [session startRunning];
    
    output.rectOfInterest = [layer metadataOutputRectOfInterestForRect:scanRect];  // 采用此方法必须放到startRunning之后
    
    [self.view bringSubviewToFront:_backButton];
    [self.view bringSubviewToFront:_startButton];
    
    _middleView = [[UIView alloc] initWithFrame:[layer rectForMetadataOutputRectOfInterest:output.rectOfInterest]];
    _middleView.backgroundColor = [UIColor clearColor];
    _middleView.layer.borderWidth = 5;
    _middleView.layer.borderColor = [UIColor orangeColor].CGColor;
    [_cameraView addSubview:_middleView];
   
//    UIView * recView = [[UIView alloc] initWithFrame:scanRect];
//    recView.backgroundColor = [UIColor clearColor];
//    recView.layer.borderWidth = 2;
//    recView.layer.borderColor = [UIColor greenColor].CGColor;
//    [_cameraView addSubview:recView];
}

- (void)captureOutput:(AVCaptureOutput *)output didOutputMetadataObjects:(NSArray<__kindof AVMetadataObject *> *)metadataObjects fromConnection:(AVCaptureConnection *)connection {
    if(metadataObjects.count > 0) {
        [session stopRunning];
        AVMetadataMachineReadableCodeObject * readCode = metadataObjects.firstObject;
        UIAlertController * alertVC = [UIAlertController alertControllerWithTitle:@"结果" message:readCode.stringValue preferredStyle:UIAlertControllerStyleAlert];
        UIAlertAction * alertAction = [UIAlertAction actionWithTitle:@"好的" style:UIAlertActionStyleDefault handler:nil];
        [alertVC addAction:alertAction];
        [self presentViewController:alertVC animated:YES completion:nil];
        NSLog(@"%@", readCode.stringValue);
    }
}

代码在这里

相关文章

网友评论

      本文标题:OC实现二维码扫描

      本文链接:https://www.haomeiwen.com/subject/yehrqftx.html