最近项目上需要开发扫描二维码进行签到的功能,主要用于开会签到的场景,所以为了避免作弊,我们再开发时只采用直接扫描的方式,并且要屏蔽从相册读取图片,此外还在二维码扫描成功签到时后台会自动上传用户的当前地点,如何自动定位获取用户的当前地点在上一篇随笔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; //解析数据进行处理并实现相应的逻辑 //代码省略 }