iOS-AVCaptureSession掃描讀取二維碼

二維碼簡單介紹

二維碼(Quick Response Code,QRCode)是一種由水平和垂直兩個方向上的線條設計而成的二維條形碼,能夠存儲數據信息,本文主要是介紹二維碼的讀取(不涉及二維碼的生成)ios

二維碼讀取

介紹

讀取二維碼就是經過掃描二維碼圖像來獲取其中的數據信息,任何條形碼的掃描都是基於視頻採集,所以須要用到AVFoundation框架objective-c

如下是AVFoundation庫的概述數組

The AVFoundation framework combines six major technology areas that together encompass a wide range of tasks for capturing, processing, synthesizing, controlling, importing and exporting audiovisual media on Apple platforms.markdown

AVFoundation框架結合了六個主要技術領域,這些領域共同涵蓋了在Apple平臺上捕獲,處理,合成,控制,導入和導出視聽媒體的普遍任務。框架

對於二維碼的讀取,咱們主要用到該庫中的Capture部分,即AVCaptureSession類,如下是其概述async

image.png 能夠看到該類繼承自NSObject,主要功能是用於管理capture(捕獲)活動並協調從輸入設備到捕獲設備的數據流ide

掃描過程概述

掃描二維碼的過程即從攝像頭捕獲二維碼圖像(input)到解析出字符串內容(output)的過程,該過程主要就是經過AVCaptureSession對象來實現oop

AVCaptureSession對象用於協調從輸入到輸出的數據流,在執行過程當中,須要先將輸入和輸出添加到該對象中,而後經過發送startRunningstopRunning消息來啓動或中止數據流,最後經過AVCaptureVideoPreviewLayer對象來將捕獲的視頻顯示在屏幕上動畫

其中,輸入對象一般是AVCaptureDeviceInput對象,經過AVCaptureDevice的實例來得到,輸出對象一般是AVCaptureMetaDataOutput對象,該對象是讀取二維碼的核心部分,須要結合AVCaptureMetaDataOutputObjectsDelegate協議結合使用,能夠捕獲在輸入設備中找到的任何元數據(metadata就是元數據的意思),並將其轉換爲字符串的格式ui

接下來咱們來結合代碼詳細說明每一個過程

具體步驟

1. 導入AVFoundation框架

#import <AVFoundation/AVFoundation.h>
複製代碼

2. 判斷權限

因爲掃描二維碼過程須要用到攝像頭,所以咱們須要設置攝像頭的權限並進行判斷

第一步:設置權限

有兩種方式設置權限

  1. 直接在info.plist文件中添加

image.png 再經過source code的方式打開,能夠看到自動添加了兩行代碼

image.png

所以咱們其實也能夠直接在源代碼中添加對應的代碼,就是下面的方法

  1. 經過source code的方式在info.plist文件中添加
<key>NSCameraUsageDescription</key>
<string>獲取相機權限</string>
複製代碼

image.png

觸類旁通,對於其餘的須要獲取權限設置也能夠經過如上兩個方式實現,例如麥克風、地理位置等,如圖

image.png

第二步:判斷權限

代碼簡寫了,核心的部分以下

#pragma mark --判斷權限
-(void)judgeAuthority{
//判斷權限的方法
    [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
        //要放到主線程中刷新
        dispatch_async(dispatch_get_main_queue(), ^{
            // 若已受權
            if (granted) {
                 //調用掃描二維碼的方法
            } else {
                //若未受權,提示彈窗
                ....
            }
        });
    }];
}
複製代碼

且其中最核心的部分就是requestAccessForMediaType:(AVMediaType)mediaType completionHandler:(void (^)(BOOL granted))handler ; 這個方法,用於請求權限,包含兩個參數

  • 第一個參數AVMediaType是媒體類型

有以下幾種類型

image.png

  • 第二個參數是一個block塊,寫相關判斷的代碼便可

關於提示彈窗,咱們用的是UIAlertController

  1. 建立UIAlertController對象
NSString *title = @"請在iPhone的「設置-隱私-相機「選項中,容許App訪問你的相機";
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"提示" message:title preferredStyle:UIAlertControllerStyleAlert];
複製代碼
  1. 建立UIAlertAction對象,即按鈕
UIAlertAction *conform = [UIAlertAction actionWithTitle:@"確認" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
                    NSLog(@"點擊了確認按鈕");
                }];
                UIAlertAction *cancel = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
                    NSLog(@"點擊了取消按鈕");
                }];
複製代碼
  1. 將按鈕添加到alert中
[alert addAction:conform];
[alert addAction:cancel];
複製代碼
  1. 顯示彈窗
[self presentViewController:alert animated:YES completion:nil];
複製代碼

顯示效果以下,首先會系統自動彈窗請求相機權限

IMG_0006.PNG

若點擊了不容許,即沒法拿到相機權限,顯示彈窗

IMG_0007.PNG

3. 建立AVCaptureSession對象

@property (nonatomic, strong) AVCaptureSession *captureSession;

_captureSession = [[AVCaptureSession alloc]init];
複製代碼

4. 爲AVCaptureSession對象添加輸入輸出

//1. 初始化設備
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];

//2. 建立輸入,基於device實例的輸入
AVCaptureDeviceInput *deviceInput = [AVCaptureDeviceInput deviceInputWithDevice:device error:nil];

//3. 建立輸出
AVCaptureMetadataOutput *metadataOutput = [[AVCaptureMetadataOutput alloc] init];

//4. 添加輸入輸出
[_captureSession addInput:deviceInput];
[_captureSession addOutput:metadataOutput];
複製代碼

5. 配置AVCaptureMetaDataOutput對象

首先是設置代理,而後是設置元數據類型

//1. 設置代理
[metadataOutput setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];

//2. 設置元數據類型,由於這裏是二維碼的掃描,因此數據類型是AVMetadataObjectTypeQRCode,注意是須要傳入數組
[metadataOutput setMetadataObjectTypes:@[AVMetadataObjectTypeQRCode]];
複製代碼

6. 建立並設置AVCaptureVideoPreviewLayer對象來顯示捕獲到的視頻

@property (nonatomic, strong) AVCaptureVideoPreviewLayer *videoPreviewLayer;//展現layer

//1. 實例化預覽塗層圖層
_videoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:_captureSession];

//2. 設置預覽圖層填充方式
[_videoPreviewLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];

//3. 設置圖層的frame
[_videoPreviewLayer setFrame:_viewPreview.layer.bounds];

//4. 將圖層添加到預覽view的圖層上
[_viewPreview.layer addSublayer:_videoPreviewLayer];

//5. 設置掃描範圍,這裏使用的是相對位置
metadataOutput.rectOfInterest = CGRectMake(0.2f, 0.2f, 0.8f, 0.8f);
複製代碼

這裏用到了rectOfInterest這個屬性,是用於設置元數據的搜索區域的,肯定矩形,矩形的座標原點位於左上角

7. 實現代理方法

#pragma mark --AVCaptureMetadataOutputObjectsDelegate
- (void)captureOutput:(AVCaptureOutput *)output didOutputMetadataObjects:(NSArray<__kindof AVMetadataObject *> *)metadataObjects fromConnection:(AVCaptureConnection *)connection{
    //判斷是否正在讀取數據
    if (!_isReading) {
        //沒有讀取,返回
        return;
    }
    //若metadataObjects.count > 0,表明掃描到二維碼
    if (metadataObjects.count > 0) {
        _isReading = NO;
        //
        AVMetadataMachineReadableCodeObject *metadataObject = metadataObjects[0];
        NSString *result = metadataObject.stringValue;
        if (self.resultBlock) {
            self.resultBlock(result);
        }
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [self.navigationController popViewControllerAnimated:YES];
    }
}
複製代碼

開發過程當中遇到的問題和解決方法

其實上面也有一些已經說了例如權限設置這些,如下還有幾個點

1. 程序運行黑屏

AppDelegate.h中添加UIWindow屬性

@property (nonatomic, strong)UIWindow *window;
複製代碼

2. 導航欄不顯示title

title屬性是從UIViewController上面繼承過來的,而不是UINavigationController上面的名字

因爲UINavigationController屬於容器,因此最少須要一個RootVIewController

而後在RootViewController的viewDidLoad設置title而不是在UINavigationController的subclass中設置

self.navigationController.title = @"掃一掃";  //本來的代碼
self.title = @"掃一掃"; //修改成self便可
複製代碼

3. 按鈕不居中

//本來的代碼,發現按鈕水平方向偏右,豎直方向」居中「
btn.frame = CGRectMake(self.view.bounds.size.width / 2.0, self.view.bounds.size.height / 2.0, 80, 40);
//解決方法,水平方向 - 40 便可(尚未糾結緣由)
btn.frame = CGRectMake(self.view.bounds.size.width / 2.0 - 40 , self.view.bounds.size.height / 2.0, 80, 40);
複製代碼

但其實有更好的方法就是直接設置center 便可

//先肯定frame,定長度和寬度
btn.frame = CGRectMake(0,0, 80, 40);
//肯定中心,即座標
btn.center = self.view.center;
複製代碼

4. block屬性傳值

簡單梳理block屬性傳值的大體代碼

//ViewController.m

//點擊按鈕時調用jumpToScanVC方法
[btn addTarget:self action:@selector(jumpToScanVC) forControlEvents:UIControlEventTouchUpInside];

-(void)jumpToScanVC{
    SecondViewController *secondVC = [[SecondViewController alloc]init];
    secondVC.secondBlock = ^(NSString * _Nonnull string) {
        self.label.text = string;
    };
    [self.navigationController pushViewController:secondVC animated:NO];
}
複製代碼
// SecondViewController.h

//定義block屬性
@interface SecondViewController : UIViewController
@property (nonatomic, copy) void(^secondBlock)(NSString *string);
@end
複製代碼
// SecondViewController.m

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    if (_secondBlock) {
        _secondBlock(@"hahaha");
    }
    [self.navigationController popViewControllerAnimated:NO];
}
複製代碼

能夠看到在第二個控制器中調用了block並將值傳遞到了第一個控制器

5. UIAlertController的用法

掃描結果顯示並彈窗,可是控制器沒有被成功pop出去,報錯顯示

popViewControllerAnimated: called on <UINavigationController 0x101827e00> while an existing transition or presentation is occurring; the navigation stack will not be updated.
複製代碼

其實就是咱們UIAlertController的彈窗動畫和pop控制器的動畫衝突了

但其實咱們的navigation stack已經pop掉了,只是沒法顯示動畫

所以咱們能夠經過延遲執行來達到先完成彈窗動畫,再完成pop動畫

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [self.navigationController popViewControllerAnimated:YES];
        });
複製代碼

6. 點擊掃描後跳轉了頁面可是沒有顯示掃描框,控制器卡死

緣由是沒有放到主線程中去刷新UI致使了卡死

image.png

7. 防止NSTimer致使沒法釋放的問題

由於設置了掃描線(其實就是一個裝飾,顯得專業一點),掃描線的移動用到了計時器,可是注意計時器的使用極可能致使內存沒法釋放的狀況,因此咱們要事先將NSTimer對象置空

#pragma mark --結束
-(void)stopRunning{
    //判判定時器是否正在工做,若還在工做另起暫停並置空
    if ([_timer isValid]) {
        //正在工做就使其失效
        [_timer invalidate];
        //並給定時器賦值nil
        _timer = nil;
    }
    [self.captureSession stopRunning];
    ...
}
複製代碼

運行界面演示

二維碼圖片

%E4%BA%8C%E7%BB%B4%E7%A0%81%E5%9B%BE%E7%89%87_5%E6%9C%8817%E6%97%A514%E6%97%B657%E5%88%8612%E7%A7%92.png

IMG_0008.PNG

IMG_0009.PNG

IMG_0010.PNG


參考博客

ios使用AVFoundation讀取二維碼的方法

iOS利用AVCaptureSession實現二維碼掃描

相關文章
相關標籤/搜索