使用 iOS 原生 API 实现二维码扫描

Posted by Calvin on 2016-09-30

使用系统的AVCaptureSessionAPI 实现的二维码扫描功能,添加有扫描动画。在以前项目中使用到的,由于之前的博客舍弃了,所以现在在新博客重新整理一下。

主要内容如下:

  • 导入 <AVFoundation/AVFoundation.h>
  • 添加 AVCaptureMetadataOutputObjectsDelegate
  • 设置扫描区域的View
  • 设置摄像头的识别区域,即可扫描的区域
  • 开始扫描并拿到扫描后的二维码信息

设置扫描区域底部遮罩

// 遮罩
_maskView = [[UIView alloc] init];
_maskView.layer.borderColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.7].CGColor;
_maskView.layer.borderWidth = kBorderW;
_maskView.bounds = CGRectMake(0, 0, Width + kBorderW + kMargin, Width + kBorderW + kMargin);
_maskView.center = self.view.center;
[self.view addSubview:_maskView];
// 补充遮罩
UIView *mask = [[UIView alloc]initWithFrame:CGRectMake(0, _maskView.frame.origin.y + _maskView.frame.size.height, Width, kBorderW + 100)];
mask.backgroundColor=[UIColor colorWithRed:0 green:0 blue:0 alpha:0.7];
[self.view addSubview:mask];
UIView *mask2 = [[UIView alloc]initWithFrame:CGRectMake(0, 0, Width, kBorderW - 19)];
mask2.backgroundColor=[UIColor colorWithRed:0 green:0 blue:0 alpha:0.7];
[self.view addSubview:mask2];
// 操作提示
tipLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, Height - kBorderW*2, self.view.bounds.size.width, kBorderW)];
tipLabel.text = @"将取景框对准二维码,即可自动扫描";
tipLabel.textColor = [UIColor colorWithRed:252 green:206 blue:74 alpha:1];
tipLabel.textAlignment = NSTextAlignmentCenter;
tipLabel.lineBreakMode = NSLineBreakByWordWrapping;
tipLabel.numberOfLines = 2;
tipLabel.font=[UIFont systemFontOfSize:14];
tipLabel.backgroundColor = [UIColor clearColor];
[self.view addSubview:tipLabel];

设置顶部导航栏

// 返回
UIButton *backBtn = [UIButton buttonWithType:UIButtonTypeCustom];
backBtn.frame = CGRectMake(20, 30, 20, 20);
[backBtn setBackgroundImage:[UIImage imageNamed:@"qrcode_scan_titlebar_back_nor"] forState:UIControlStateNormal];
backBtn.contentMode=UIViewContentModeScaleAspectFit;
[backBtn addTarget:self action:@selector(disMiss) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:backBtn];
// 闪光灯
flashBtn = [UIButton buttonWithType:UIButtonTypeCustom];
flashBtn.frame = CGRectMake(0,0, 60, 78);
flashBtn.center = CGPointMake(Width/2, Height - 100);
[flashBtn setBackgroundImage:[UIImage imageNamed:@"qrcode_scan_btn_flash_down"] forState:UIControlStateNormal];
[flashBtn setBackgroundImage:[UIImage imageNamed:@"qrcode_scan_btn_scan_off"] forState:UIControlStateSelected];
flashBtn.contentMode = UIViewContentModeScaleAspectFit;
[flashBtn addTarget:self action:@selector(openFlash:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:flashBtn];

扫描二维码扫描区域

CGFloat scanWindowH = Width - kMargin * 2;
CGFloat scanWindowW = Width - kMargin * 2;
_scanWindow = [[UIView alloc] initWithFrame:CGRectMake(0,0, scanWindowW, scanWindowH)];
_scanWindow.center = self.view.center;
_scanWindow.clipsToBounds = YES;
[self.view addSubview:_scanWindow];
_scanNetImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"scan_net"]];
_scanNetImageView.frame = _scanWindow.frame;
_scanNetImageView.center = self.view.center;
CGFloat buttonWH = 18;
[_scanWindow addSubview:_scanNetImageView];
UIButton *topLeft = [[UIButton alloc] initWithFrame:CGRectMake(1, 0, buttonWH, buttonWH)];
[topLeft setImage:[UIImage imageNamed:@"scan_1"] forState:UIControlStateNormal];
[_scanWindow addSubview:topLeft];
UIButton *topRight = [[UIButton alloc] initWithFrame:CGRectMake(scanWindowW - buttonWH - 1, 0, buttonWH, buttonWH)];
[topRight setImage:[UIImage imageNamed:@"scan_2"] forState:UIControlStateNormal];
[_scanWindow addSubview:topRight];
UIButton *bottomLeft = [[UIButton alloc] initWithFrame:CGRectMake(1, scanWindowH - buttonWH, buttonWH, buttonWH)];
[bottomLeft setImage:[UIImage imageNamed:@"scan_3"] forState:UIControlStateNormal];
[_scanWindow addSubview:bottomLeft];
UIButton *bottomRight = [[UIButton alloc] initWithFrame:CGRectMake(topRight.frame.origin.x, bottomLeft.frame.origin.y, buttonWH, buttonWH)];
[bottomRight setImage:[UIImage imageNamed:@"scan_4"] forState:UIControlStateNormal];
[_scanWindow addSubview:bottomRight];

开始扫描

#pragma mark -
#pragma mark - 开始扫描
- (void)beginScanning{
// 获取摄像设备
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
// 创建输入流
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device error:nil];
if (!input) return;
// 创建输出流
AVCaptureMetadataOutput *output = [[AVCaptureMetadataOutput alloc]init];
// 设置代理 在主线程里刷新
[output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
// 设置有效扫描区域
CGRect scanCrop = [self getScanCrop:_scanWindow.bounds readerViewBounds:self.view.frame];
output.rectOfInterest = scanCrop;
// 初始化链接对象
_session = [[AVCaptureSession alloc]init];
// 高质量采集率
[_session setSessionPreset:AVCaptureSessionPresetHigh];
[_session addInput:input];
[_session addOutput:output];
// 设置扫码支持的编码格式(如下设置条形码和二维码兼容)
output.metadataObjectTypes = @[AVMetadataObjectTypeQRCode,AVMetadataObjectTypeEAN13Code, AVMetadataObjectTypeEAN8Code, AVMetadataObjectTypeCode128Code];
AVCaptureVideoPreviewLayer *layer = [AVCaptureVideoPreviewLayer layerWithSession:_session];
layer.videoGravity = AVLayerVideoGravityResizeAspectFill;
layer.frame = self.view.layer.bounds;
[self.view.layer insertSublayer:layer atIndex:0];
// 开始捕获
[_session startRunning];
}
#pragma mark --
#pragma mark -- 获取扫描区域的比例关系
-(CGRect)getScanCrop:(CGRect)rect readerViewBounds:(CGRect)readerViewBounds
{
CGFloat x,y,width,height;
x = (CGRectGetHeight(readerViewBounds)-CGRectGetHeight(rect))/2/CGRectGetHeight(readerViewBounds);
y = (CGRectGetWidth(readerViewBounds)-CGRectGetWidth(rect))/2/CGRectGetWidth(readerViewBounds);
width = CGRectGetHeight(rect)/CGRectGetHeight(readerViewBounds);
height = CGRectGetWidth(rect)/CGRectGetWidth(readerViewBounds);
return CGRectMake(x, y, width, height);
}

通过代理方法拿到摄像头扫描的二维码信息

#pragma mark --
#pragma mark -- 获取二维码
-(void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection{
if (metadataObjects.count > 0) {
[_session stopRunning];
AVMetadataMachineReadableCodeObject *metadataObject = [metadataObjects objectAtIndex : 0 ];
// 二维码信息
NSString *str = metadataObject.stringValue;
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@"二维码信息:" message:str delegate:self cancelButtonTitle:@"取消" otherButtonTitles:@"确定", nil];
[alert show];
}
}

最后拿到扫描到的二维码信息并展示,当然你也可以做其它处理。其中加入了一些动画,主要是扫描界面的遮罩。当然,别忘记在 info.plis 文件中添加 Privacy - Camera Usage Description 字段和描述。

附上:代码地址

扫描效果图: