iOS學習——iOS原生實現二維碼掃描

  最近項目上須要開發掃描二維碼進行簽到的功能,主要用於開會簽到的場景,因此爲了不做弊,咱們再開發時只採用直接掃描的方式,而且要屏蔽從相冊讀取圖片,此外還在二維碼掃描成功簽到時後臺會自動上傳用戶的當前地點,如何自動定位獲取用戶的當前地點在上一篇隨筆iOS學習——自動定位中已經講過了,本文就簡單地說一下如何利用iOS原生的模塊實現二維碼的掃描。html

  二維碼掃描是不少應用都會實現的功能,比較著名的第三方開源庫是Google出品的ZXing,其的OC的移植版本是ZXingObjc。iOS系統原生的二維碼掃描模塊是在iOS7以後推出的,它主要是利用iOS設備的後置攝像頭進行實現的。git

  要調用系統的攝像頭識別二維碼,咱們須要導入系統的AVFoundation庫。使用系統的攝像頭,咱們通常的須要如下五個對象:一個後置攝像頭設備(AVCaptureDevice)、一個輸入(AVCaptureDeviceInput)、一個輸出(AVCaptureMetadataOutput)、一個協調控制器(AVCaptureSession)、一個預覽層(AVCaptureVideoPreviewLayer),此外爲了更好的體驗效果,咱們加入了縮放手勢,在進行二維碼掃描的時候能夠手動進行縮放掃描區域,以得到更好的掃描效果。github

@interface CJScanQRCodeViewController () <AVCaptureMetadataOutputObjectsDelegate>

@property (strong, nonatomic) AVCaptureDevice * device; //捕獲設備,默認後置攝像頭
@property (strong, nonatomic) AVCaptureDeviceInput * input; //輸入設備
@property (strong, nonatomic) AVCaptureMetadataOutput * output;//輸出設備,須要指定他的輸出類型及掃描範圍
@property (strong, nonatomic) AVCaptureSession * session; //AVFoundation框架捕獲類的中心樞紐,協調輸入輸出設備以得到數據
@property (strong, nonatomic) AVCaptureVideoPreviewLayer * previewLayer;//展現捕獲圖像的圖層,是CALayer的子類

@property (strong, nonatomic) UIPinchGestureRecognizer *pinchGes;//縮放手勢
@property (assign, nonatomic) CGFloat scanRegion_W;//二維碼正方形掃描區域的寬度,根據不一樣機型適配

@end

  首先,咱們是須要進行對咱們的一些設備進行配置,比喻須要用到自動定位,就須要對定位信息進行配置,接着對二維碼掃描的相關設備進行配置,而後對咱們的縮放手勢進行設置,都配置完以後,直接開始啓動二維碼掃描就能夠了,成功掃碼並識別到信息時候會調用對應的 AVCaptureMetadataOutputObjectsDelegate 代理的 - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection 方法進行後期處理,咱們須要實現代理的該方法,在其中編寫咱們須要的功能邏輯。數組

- (void)viewDidLoad {
    [super viewDidLoad];
    //頁面標題
    self.title = @"掃一掃";
    //配置定位信息
    [self configLocation];
    //配置二維碼掃描
    [self configBasicDevice];
    //配置縮放手勢
    [self configPinchGes];
    //開始啓動
    [self.session startRunning];
}

  關於二維碼掃描設備的配置流程,通常地,咱們先將須要的五大設備進行初始化,而後須要進行對應的設置沒具體的設置流程和方法見下面的代碼和註釋。session

- (void)configBasicDevice{
    //默認使用後置攝像頭進行掃描,使用AVMediaTypeVideo表示視頻
    self.device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
    //設備輸入 初始化
    self.input = [[AVCaptureDeviceInput alloc]initWithDevice: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:AVCaptureSessionPresetHigh];
    //會話添加設備的 輸入 輸出,創建鏈接
    if ([self.session canAddInput:self.input]) {
        [self.session addInput:self.input];
    }
    if ([self.session canAddOutput:self.output]) {
        [self.session addOutput:self.output];
    }
    //指定設備的識別類型 這裏只指定二維碼識別這一種類型 AVMetadataObjectTypeQRCode
    //指定識別類型這一步必定要在輸出添加到會話以後,不然設備的課識別類型會爲空,程序會出現崩潰
    [self.output setMetadataObjectTypes:@[AVMetadataObjectTypeQRCode]];
    //設置掃描信息的識別區域,本文設置正中央的一塊正方形區域,該區域寬度是scanRegion_W
    //這裏考慮了導航欄的高度,因此計算有點麻煩,識別區域越小識別效率越高,因此不設置整個屏幕
    CGFloat navH = self.navigationController.navigationBar.bounds.size.height;
    CGFloat viewH = ZYAppHeight - navH;
    CGFloat scanViewH = self.scanRegion_W;
    [self.output setRectOfInterest:CGRectMake((ZYAppWidth-scanViewH)/(2*ZYAppWidth), (viewH-scanViewH)/(2*viewH), scanViewH/ZYAppWidth, scanViewH/viewH)];
    //預覽層 初始化,self.session負責驅動input進行信息的採集,layer負責把圖像渲染顯示
    //預覽層的區域設置爲整個屏幕,這樣能夠方便咱們進行移動二維碼到掃描區域,在上面咱們已經對咱們的掃描區域進行了相應的設置
    self.previewLayer = [[AVCaptureVideoPreviewLayer alloc]initWithSession:self.session];
    self.previewLayer.frame = CGRectMake(0, 0, ZYAppWidth, ZYAppHeight);
    self.previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
    [self.view.layer addSublayer:self.previewLayer];
    //掃描框 和掃描線的佈局和設置,模擬正在掃描的過程,這一塊加不加不影響咱們的效果,只是起一個直觀的做用
    TNWCameraScanView *clearView = [[TNWCameraScanView alloc]initWithFrame:self.view.frame navH:navH];
    [self.view addSubview:clearView];
    //掃描框下面的信息label佈局
    UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, (viewH+scanViewH)/2+10.0f, ZYAppWidth, 20.0f)];
    label.text = @"掃一掃功能僅用於會議簽到";
    label.font = FONT(15.0f);
    label.textColor = [UIColor whiteColor];
    label.textAlignment = NSTextAlignmentCenter;
    [self.view addSubview:label];
}

  接下來咱們看一下如何配置咱們的縮放手勢,這個相對而言就很簡單了,咱們直接在self.view上添加一個縮放手勢,並在對應的方法中對咱們的相機設備的焦距進行修改就達到了縮放的目的。框架

- (void)configPinchGes{
    self.pinchGes = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinchDetected:)];
    [self.view addGestureRecognizer:self.pinchGes];
}

- (void)pinchDetected:(UIPinchGestureRecognizer*)recogniser{
    if (!_device){
        return;
    }
   //對手勢的狀態進行判斷
    if (recogniser.state == UIGestureRecognizerStateBegan){
        _initScale = _device.videoZoomFactor;
    }
    //相機設備在改變某些參數前必須先鎖定,直到改變結束才能解鎖
    NSError *error = nil;
    [_device lockForConfiguration:&error]; //鎖定相機設備
    if (!error) {
        CGFloat zoomFactor; //縮放因子
        CGFloat scale = recogniser.scale;
        if (scale < 1.0f) {
            zoomFactor = self.initScale - pow(self.device.activeFormat.videoMaxZoomFactor, 1.0f - recogniser.scale);
        } else {
            zoomFactor = self.initScale + pow(self.device.activeFormat.videoMaxZoomFactor, (recogniser.scale - 1.0f) / 2.0f);
        }
        zoomFactor = MIN(15.0f, zoomFactor);
        zoomFactor = MAX(1.0f, zoomFactor);
        _device.videoZoomFactor = zoomFactor;
        [_device unlockForConfiguration];
    }
} 

  最後,咱們須要重寫代理的回調方法,實現咱們在成功識別二維碼以後要實現的功能邏輯。這樣咱們的二維碼掃描功能就完成了。ide

#pragma mark - AVCaptureMetadataOutputObjectsDelegate
//後置攝像頭掃描到二維碼的信息
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection{
    [self.session stopRunning];   //中止掃描
    if ([metadataObjects count] >= 1) {
        //數組中包含的都是AVMetadataMachineReadableCodeObject 類型的對象,該對象中包含解碼後的數據
        AVMetadataMachineReadableCodeObject *qrObject = [metadataObjects lastObject];
        //拿到掃描內容在這裏進行個性化處理
        NSString *result = qrObject.stringValue;
        //解析數據進行處理並實現相應的邏輯
        //代碼省略
}
相關文章
相關標籤/搜索