最近項目上需要開發掃描二維碼進行簽到的功能,主要用於開會簽到的場景,所以為了避免作弊,我們再開發時只採用直接掃描的方式,並且要屏蔽從相冊讀取圖片,此外還在二維碼掃描成功簽到時後臺會自動上傳用戶的當前地點,如何自動定位獲取用戶的當前地點在上一篇隨筆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; //解析數據進行處理並實現相應的邏輯 //代碼省略 }