二維碼(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
能夠看到該類繼承自NSObject,主要功能是用於管理capture(捕獲)活動並協調從輸入設備到捕獲設備的數據流ide
掃描二維碼的過程即從攝像頭捕獲二維碼圖像(input)到解析出字符串內容(output)的過程,該過程主要就是經過AVCaptureSession對象來實現oop
AVCaptureSession
對象用於協調從輸入到輸出的數據流,在執行過程當中,須要先將輸入和輸出添加到該對象中,而後經過發送startRunning
和stopRunning
消息來啓動或中止數據流,最後經過AVCaptureVideoPreviewLayer
對象來將捕獲的視頻顯示在屏幕上動畫
其中,輸入對象一般是AVCaptureDeviceInput
對象,經過AVCaptureDevice
的實例來得到,輸出對象一般是AVCaptureMetaDataOutput
對象,該對象是讀取二維碼的核心部分,須要結合AVCaptureMetaDataOutputObjectsDelegate
協議結合使用,能夠捕獲在輸入設備中找到的任何元數據(metadata就是元數據的意思),並將其轉換爲字符串的格式ui
接下來咱們來結合代碼詳細說明每一個過程
#import <AVFoundation/AVFoundation.h>
複製代碼
因爲掃描二維碼過程須要用到攝像頭,所以咱們須要設置攝像頭的權限並進行判斷
第一步:設置權限
有兩種方式設置權限
再經過source code的方式打開,能夠看到自動添加了兩行代碼
所以咱們其實也能夠直接在源代碼中添加對應的代碼,就是下面的方法
<key>NSCameraUsageDescription</key>
<string>獲取相機權限</string>
複製代碼
觸類旁通,對於其餘的須要獲取權限設置也能夠經過如上兩個方式實現,例如麥克風、地理位置等,如圖
第二步:判斷權限
代碼簡寫了,核心的部分以下
#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
是媒體類型有以下幾種類型
關於提示彈窗,咱們用的是UIAlertController
UIAlertController
對象NSString *title = @"請在iPhone的「設置-隱私-相機「選項中,容許App訪問你的相機";
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"提示" message:title preferredStyle:UIAlertControllerStyleAlert];
複製代碼
UIAlertAction
對象,即按鈕UIAlertAction *conform = [UIAlertAction actionWithTitle:@"確認" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
NSLog(@"點擊了確認按鈕");
}];
UIAlertAction *cancel = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
NSLog(@"點擊了取消按鈕");
}];
複製代碼
[alert addAction:conform];
[alert addAction:cancel];
複製代碼
[self presentViewController:alert animated:YES completion:nil];
複製代碼
顯示效果以下,首先會系統自動彈窗請求相機權限
若點擊了不容許,即沒法拿到相機權限,顯示彈窗
@property (nonatomic, strong) AVCaptureSession *captureSession;
_captureSession = [[AVCaptureSession alloc]init];
複製代碼
//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];
複製代碼
首先是設置代理,而後是設置元數據類型
//1. 設置代理
[metadataOutput setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
//2. 設置元數據類型,由於這裏是二維碼的掃描,因此數據類型是AVMetadataObjectTypeQRCode,注意是須要傳入數組
[metadataOutput setMetadataObjectTypes:@[AVMetadataObjectTypeQRCode]];
複製代碼
@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
這個屬性,是用於設置元數據的搜索區域的,肯定矩形,矩形的座標原點位於左上角
#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];
}
}
複製代碼
其實上面也有一些已經說了例如權限設置這些,如下還有幾個點
在AppDelegate.h
中添加UIWindow
屬性
@property (nonatomic, strong)UIWindow *window;
複製代碼
title屬性是從UIViewController上面繼承過來的,而不是UINavigationController上面的名字
因爲UINavigationController屬於容器,因此最少須要一個RootVIewController
而後在RootViewController的viewDidLoad設置title而不是在UINavigationController的subclass中設置
self.navigationController.title = @"掃一掃"; //本來的代碼
self.title = @"掃一掃"; //修改成self便可
複製代碼
//本來的代碼,發現按鈕水平方向偏右,豎直方向」居中「
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;
複製代碼
簡單梳理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並將值傳遞到了第一個控制器
掃描結果顯示並彈窗,可是控制器沒有被成功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];
});
複製代碼
緣由是沒有放到主線程中去刷新UI致使了卡死
由於設置了掃描線(其實就是一個裝飾,顯得專業一點),掃描線的移動用到了計時器,可是注意計時器的使用極可能致使內存沒法釋放的狀況,因此咱們要事先將NSTimer對象置空
#pragma mark --結束
-(void)stopRunning{
//判判定時器是否正在工做,若還在工做另起暫停並置空
if ([_timer isValid]) {
//正在工做就使其失效
[_timer invalidate];
//並給定時器賦值nil
_timer = nil;
}
[self.captureSession stopRunning];
...
}
複製代碼
二維碼圖片
參考博客