CNBBASMScanViewController.m 16.3 KB
//
//  BBASMScanViewController.m
//  SwanAppDemo
//
//  Created by Ren,Tao on 2019/1/12.
//  Copyright © 2019 baidu. All rights reserved.
//

#import "CNBBASMScanViewController.h"
#import <AssetsLibrary/ALAssetsLibrary.h>
#import <Photos/PHPhotoLibrary.h>
#import <AVFoundation/AVFoundation.h>
#import "CNBBASMScanView.h"

@interface CNBBASMScanViewController ()<UINavigationControllerDelegate, UIImagePickerControllerDelegate, AVCaptureMetadataOutputObjectsDelegate, UIGestureRecognizerDelegate>

@property (nonatomic, strong) CNBBASMScanView *scanRectView;
@property (nonatomic, strong) UILabel *tipTitle;  //扫码区域下方提示文字
@property (nonatomic, strong) UIBarButtonItem *photoItem;   //相册按钮
@property (nonatomic, strong) UIButton *flashBtn; //闪光灯按钮

@property (nonatomic, strong) AVCaptureDevice            *device;
@property (nonatomic, strong) AVCaptureDeviceInput       *input;
@property (nonatomic, strong) AVCaptureMetadataOutput    *output;
@property (nonatomic, strong) AVCaptureSession           *session;
@property (nonatomic, strong) AVCaptureVideoPreviewLayer *preview;

// @{@"codeResult": 扫码结果, @"scanType":扫码类型}
@property (nonatomic, copy) void (^scanFinish)(NSDictionary *, NSError *);

@end

@implementation CNBBASMScanViewController

- (instancetype)initScanCompleteBlock:(void (^)(NSDictionary *result, NSError *error))scanBlock {
    self = [super init];
    if (self) {
        self.scanFinish = scanBlock;
    }
    return self;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    self.title = @"扫一扫";
    self.view.backgroundColor = [UIColor blackColor];
    
    [self initScanDevide];
    [self drawTitle];
    [self drawFlashBtn];
    [self drawScanView];
    
    _photoItem = [[UIBarButtonItem alloc] initWithTitle:@"相册" style:UIBarButtonItemStylePlain target:self action:@selector(openPhoto)];
    [self.navigationItem setRightBarButtonItem:_photoItem];
    
    _tipTitle.text = @"将取景框对准二维码,即可自动扫描";
    _tipTitle.center = CGPointMake(self.view.center.x, self.view.center.y + CGSizeFromString([self scanRectWithScale:1][1]).height/2 + 25);
    [self.view bringSubviewToFront:_tipTitle];
    [self.view bringSubviewToFront:_flashBtn];
}

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    // 开始捕获
    if (self.session) [self.session startRunning];
}

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    
    // 打开系统右滑移动返回手势
    if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
        self.navigationController.interactivePopGestureRecognizer.enabled = YES;      // 手势有效设置为YES  无效为NO
        self.navigationController.interactivePopGestureRecognizer.delegate = self;    // 手势的代理设置为self
    }
    //开始捕获
    if (self.session) [self.session stopRunning];
}

- (void)initScanDevide {
    if ([self isAvailableCamera]) {
        // 初始化摄像设备
        self.device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
        // 初始化摄像输入流
        self.input = [AVCaptureDeviceInput deviceInputWithDevice:self.device error:nil];
        // 初始化摄像输出流
        self.output = [[AVCaptureMetadataOutput alloc] init];
        // 设置输出代理,在主线程里刷新
        [self.output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
        
        // 初始化链接对象
        self.session = [[AVCaptureSession alloc] init];
        // 设置采集质量
        [self.session setSessionPreset:AVCaptureSessionPresetInputPriority];
        // 将输入输出流对象添加到链接对象
        if ([self.session canAddInput:self.input]) [self.session addInput:self.input];
        if ([self.session canAddOutput:self.output]) [self.session addOutput:self.output];
        
        // 设置扫码支持的编码格式
        self.output.metadataObjectTypes = @[AVMetadataObjectTypeEAN8Code,
                                            AVMetadataObjectTypeUPCECode,
                                            AVMetadataObjectTypeEAN13Code,
                                            AVMetadataObjectTypeInterleaved2of5Code,
                                            AVMetadataObjectTypeCode39Code,
                                            AVMetadataObjectTypeCode128Code,
                                            AVMetadataObjectTypeQRCode,
                                            AVMetadataObjectTypeDataMatrixCode,
                                            AVMetadataObjectTypeAztecCode];
        // 设置扫描聚焦区域
        self.output.rectOfInterest = CGRectFromString([self scanRectWithScale:1][0]);
        
        self.preview = [AVCaptureVideoPreviewLayer layerWithSession:self.session];
        self.preview.videoGravity = AVLayerVideoGravityResizeAspectFill;
        self.preview.frame = [UIScreen mainScreen].bounds;
        [self.view.layer insertSublayer:self.preview atIndex:0];
    }
}

- (NSArray *)scanRectWithScale:(NSInteger)scale {
    CGSize windowSize = [UIScreen mainScreen].bounds.size;
    CGFloat Left = 60 / scale;
    CGSize scanSize = CGSizeMake(self.view.frame.size.width - Left * 2, (self.view.frame.size.width - Left * 2) / scale);
    CGRect scanRect = CGRectMake((windowSize.width-scanSize.width)/2, (windowSize.height-scanSize.height)/2, scanSize.width, scanSize.height);
    
    scanRect = CGRectMake(scanRect.origin.y/windowSize.height, scanRect.origin.x/windowSize.width, scanRect.size.height/windowSize.height,scanRect.size.width/windowSize.width);
    return @[NSStringFromCGRect(scanRect), NSStringFromCGSize(scanSize)];
}

- (void)scanFinishResult:(NSDictionary *)resDic {
    if (self.scanFinish) {
        //回调结果到页面上,也可以在此处做跳转操作,如果不想回去,直接注释下面的代码
        if (self.navigationController &&[self.navigationController respondsToSelector:@selector(popViewControllerAnimated:)]) {
            [self.navigationController popViewControllerAnimated:YES];
            if (resDic) {
                self.scanFinish(resDic, nil);
            } else {
                NSError *error = [NSError errorWithDomain:@"扫码失败"
                                                     code:-1
                                                 userInfo:nil];
                self.scanFinish(resDic, error);
            }
        }
    }
}

#pragma mark- draw subView

- (void)drawFlashBtn {
    if (_flashBtn) {
        return;
    }
    
    NSBundle *scanBundle = [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:@"demo" ofType: @"bundle"]];
    _flashBtn = [UIButton buttonWithType:UIButtonTypeCustom];
    [_flashBtn setBounds:CGRectMake(0, 0, 60, 50)];
    CGFloat flash_y = ([UIScreen mainScreen].bounds.size.width / 320 * 70);
    [_flashBtn setCenter:CGPointMake(self.view.center.x, self.view.center.y + flash_y)];
    [_flashBtn setImage:[UIImage imageNamed:@"scan_flash_normal"] forState:UIControlStateNormal];
    [_flashBtn setImage:[UIImage imageNamed:@"scan_flash_select"] forState:UIControlStateSelected];
    [_flashBtn setTitle:@"轻触照亮" forState:UIControlStateNormal];
    [_flashBtn setTitle:@"轻触关闭" forState:UIControlStateSelected];
    [_flashBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
    [_flashBtn setTitleColor:[UIColor colorWithRed:0.161 green:0.659 blue:0.882 alpha:1.00] forState:UIControlStateSelected];
    [_flashBtn addTarget:self action:@selector(openFlash:) forControlEvents:UIControlEventTouchDown];
    _flashBtn.titleLabel.font = [UIFont systemFontOfSize:11];
    // button标题的偏移量以及图片的偏移量,以便于上下呈现
    _flashBtn.titleEdgeInsets = UIEdgeInsetsMake(
                                                 _flashBtn.imageView.frame.size.height+5,
                                                 -_flashBtn.imageView.bounds.size.width,
                                                 0,
                                                 0
                                                 );
    
    _flashBtn.imageEdgeInsets = UIEdgeInsetsMake(
                                                 0,
                                                 _flashBtn.titleLabel.frame.size.width/2,
                                                 _flashBtn.titleLabel.frame.size.height+5,
                                                 -_flashBtn.titleLabel.frame.size.width/2
                                                 );
    [self.view addSubview:_flashBtn];
}

// 绘制扫描区域
- (void)drawScanView {
    _scanRectView = [[CNBBASMScanView alloc] initWithFrame:self.view.frame];
    [self.view addSubview:_scanRectView];
}

- (void)drawTitle {
    if (!_tipTitle) {
        _tipTitle = [[UILabel alloc]init];
        _tipTitle.bounds = CGRectMake(0, 0, 300, 50);
        _tipTitle.center = CGPointMake(CGRectGetWidth(self.view.frame)/2, self.view.center.y + self.view.frame.size.width/2 - 35);
        _tipTitle.font = [UIFont systemFontOfSize:13];
        _tipTitle.textAlignment = NSTextAlignmentCenter;
        _tipTitle.numberOfLines = 0;
        _tipTitle.text = @"将取景框对准二维码,即可自动扫描";
        _tipTitle.textColor = [UIColor whiteColor];
        [self.view addSubview:_tipTitle];
    }
    _tipTitle.layer.zPosition = 1;
    [self.view bringSubviewToFront:_tipTitle];
}

#pragma mark - photo

//打开相册
- (void)openPhoto {
    if ([self isAvailablePhoto]) {
        [self openPhotoLibrary];
    } else {
        NSString *appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"];
        NSString *tipMessage = [NSString stringWithFormat:@"请到手机系统的\n【设置】->【隐私】->【相册】\n\"%@\"开启相机的访问权限",appName];
        [self showError:tipMessage andTitle:@"相册读取权限未开启"];
    }
}

- (void)openPhotoLibrary {
    UIImagePickerController *picker = [[UIImagePickerController alloc] init];
    picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
    picker.delegate = self;
    picker.allowsEditing = YES;
    [self presentViewController:picker animated:YES completion:nil];
}

#pragma mark - 闪光灯开启、关闭
- (void)openFlash:(UIButton *)sender {
    sender.selected = !sender.selected;
    AVCaptureDevice *device =  [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
    if ([device hasTorch] && [device hasFlash]) {
        AVCaptureTorchMode torch = self.input.device.torchMode;
        switch (_input.device.torchMode) {
            case AVCaptureTorchModeAuto:
                break;
            case AVCaptureTorchModeOff:
                torch = AVCaptureTorchModeOn;
                break;
            case AVCaptureTorchModeOn:
                torch = AVCaptureTorchModeOff;
                break;
            default:
                break;
        }
        [_input.device lockForConfiguration:nil];
        _input.device.torchMode = torch;
        [_input.device unlockForConfiguration];
    }
}


#pragma mark - UIImagePickerControllerDelegate
-(void)imagePickerController:(UIImagePickerController*)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
    [picker dismissViewControllerAnimated:YES completion:nil];
    __block UIImage* image = [info objectForKey:UIImagePickerControllerEditedImage];
    if (!image){
        image = [info objectForKey:UIImagePickerControllerOriginalImage];
    }
    __weak typeof(self) weakSelf = self;
    [self recognizeQrCodeImage:image onFinish:^(NSString *result) {
        NSDictionary *resDic = nil;
        if (result) {
            // AVMetadataObjectTypeQRCode
            resDic = @{@"codeResult":result, @"scanType":@(10)};
        }
        [weakSelf scanFinishResult:resDic];
    }];
}

- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker {
    [picker dismissViewControllerAnimated:YES completion:nil];
}

#pragma mark - AVCaptureMetadataOutputObjectsDelegate

- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection {
    NSDictionary *resDic = nil;
    if (metadataObjects.count > 0) {
        [self.session stopRunning];
        AVMetadataMachineReadableCodeObject *metadataObject = metadataObjects.firstObject;
        if (metadataObject.stringValue) {
            NSInteger barcodeType = [self scanBarcodeType:metadataObject.type];
            resDic = @{@"codeResult":metadataObject.stringValue,
                       @"scanType":@(barcodeType)};
        }
    } else {
        [self showError:@"图片中未识别到二维码"];
    }
    [self scanFinishResult:resDic];
}

#pragma mark - 相册与相机是否可用
- (BOOL)isAvailablePhoto {
    PHAuthorizationStatus authorStatus = [PHPhotoLibrary authorizationStatus];
    if (authorStatus == PHAuthorizationStatusDenied) {
        return NO;
    }
    return YES;
}

- (BOOL)isAvailableCamera {
    if([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
        // 用户是否允许摄像头使用
        NSString *mediaType = AVMediaTypeVideo;
        AVAuthorizationStatus authorizationStatus = [AVCaptureDevice authorizationStatusForMediaType:mediaType];
        // 不允许弹出提示框
        if (authorizationStatus == AVAuthorizationStatusRestricted ||
            authorizationStatus == AVAuthorizationStatusDenied) {
            NSString *appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"];
            NSString *tipMessage = [NSString stringWithFormat:@"请到手机系统的\n【设置】->【隐私】->【相机】\n\"%@\"开启相机的访问权限",appName];
            [self showError:tipMessage andTitle:@"相机权限未开启"];
            return NO;
        }else{
            return YES;
        }
    } else {
        // 相机硬件不可用【一般是模拟器】
        return NO;
    }
}

#pragma mark - Error handle
- (void)showError:(NSString*)str {
    [self showError:str andTitle:@"提示"];
}

- (void)showError:(NSString*)str andTitle:(NSString *)title {
    [self.session stopRunning];
    __weak typeof(self) weakSelf = self;
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:title message:str preferredStyle:UIAlertControllerStyleAlert];
    UIAlertAction *action = ({
        UIAlertAction *action = [UIAlertAction actionWithTitle:@"知道了" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
            [weakSelf.session startRunning];
        }];
        action;
    });
    [alert addAction:action];
    [self presentViewController:alert animated:YES completion:NULL];
}

#pragma mark - 识别二维码
- (void)recognizeQrCodeImage:(UIImage *)image onFinish:(void (^)(NSString *result))finish {
    if ([[[UIDevice currentDevice] systemVersion] floatValue] < 8.0 ) {
        [self showError:@"只支持iOS8.0以上系统"];
        return;
    }
    //系统自带识别方法
    CIContext *context = [CIContext contextWithOptions:nil];
    CIDetector *detector = [CIDetector detectorOfType:CIDetectorTypeQRCode context:context options:@{ CIDetectorAccuracy : CIDetectorAccuracyHigh}];
    NSArray *features = [detector featuresInImage:[CIImage imageWithCGImage:image.CGImage]];
    if (features.count >= 1) {
        CIQRCodeFeature *feature = [features objectAtIndex:0];
        NSString *scanResult = feature.messageString;
        if (finish) {
            finish(scanResult);
        }
    } else {
        [self showError:@"图片中未识别到二维码"];
    }
}

- (NSInteger)scanBarcodeType:(AVMetadataObjectType)objType {
    NSInteger barcode = 0;
    if (objType == AVMetadataObjectTypeEAN8Code) {
        barcode = 1;
    } else if (objType == AVMetadataObjectTypeUPCECode) {
        barcode = 2;
    } else if (objType == AVMetadataObjectTypeEAN13Code) {
        barcode = 5;
    } else if (objType == AVMetadataObjectTypeInterleaved2of5Code) {
        barcode = 7;
    } else if (objType == AVMetadataObjectTypeCode39Code) {
        barcode = 8;
    } else if (objType == AVMetadataObjectTypeCode128Code) {
        barcode = 9;
    } else if (objType == AVMetadataObjectTypeQRCode) {
        barcode = 10;
    } else if (objType == AVMetadataObjectTypeDataMatrixCode) {
        barcode = 11;
    } else if (objType == AVMetadataObjectTypeAztecCode) {
        barcode = 12;
    }
    return barcode;
}

@end