iOS學習——iOS原生實現二維碼掃描,ios
最近項目上需要開發掃描二維碼進行簽到的功能,主要用於開會簽到的情境,所以為了避免作弊,我們再開發時只採用直接掃描的方式,並且要屏蔽從相簿讀取圖片,此外還在二維碼掃描成功簽到時後台會自動上傳使用者的當前地點,如何自動定位擷取使用者的當前地點在上一篇隨筆iOS學習——自動定位中已經講過了,本文就簡單地說一下如何利用iOS原生的模組實現二維碼的掃描。
二維碼掃描是很多應用都會實現的功能,比較著名的第三方開源庫是Google出品的ZXing,其的OC的移植版本是ZXingObjc。iOS系統原生的二維碼掃描模組是在iOS7之後推出的,它主要是利用iOS裝置的後置網路攝影機進行實現的。
要調用系統的網路攝影機識別二維碼,我們需要匯入系統的AVFoundation庫。使用系統的網路攝影機,我們一般的需要以下五個對象:一個後置網路攝影機裝置(AVCaptureDevice)、一個輸入(AVCaptureDeviceInput)、一個輸出(AVCaptureMetadataOutput)、一個協調控制器(AVCaptureSession)、一個預覽層(AVCaptureVideoPreviewLayer),此外為了更好的體驗效果,我們加入了縮放手勢,在進行二維碼掃描的時候可以手動進行縮放掃描地區,以獲得更好的掃描效果。
@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];}
關於二維碼掃描裝置的配置流程,一般地,我們先將需要的五大裝置進行初始化,然後需要進行對應的設定沒具體的設定流程和方法見下面的代碼和注釋。
- (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]; }}
最後,我們需要重寫代理的回調方法,實現我們在成功識別二維碼之後要實現的功能邏輯。這樣我們的二維碼掃描功能就完成了。
#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; //解析資料進行處理並實現相應的邏輯 //代碼省略}