博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
iOS开发之扫描二维码
阅读量:7233 次
发布时间:2019-06-29

本文共 7927 字,大约阅读时间需要 26 分钟。

自iOS7以后,iOS扫描二维码不需要借助于第三方框架了,苹果在AVFoundation中原生支持了扫描二维码的API,主要涉及到5个类,这5个类在自定义相机或者视频时也用得上,网上有很多介绍,这5个类分别为:

AVCaptureSession:媒体捕获会话,负责把捕获的音视频数据输出到输出设备中。AVCaptureDevice:输入设备,如麦克风、摄像头。AVCaptureDeviceInput:设备输入数据管理对象,可以根据AVCaptureDevice创建对应的AVCaptureDeviceInput对象,该对象将会被添加到AVCaptureSession中管理。AVCaptureOutput:输出数据管理对象,用于接收各类输出数据,有很多子类,每个子类用途都不一样,该对象将会被添加到AVCaptureSession中管理。AVCaptureVideoPreviewLayer:相机拍摄预览图层,是CALayer的子类,使用该对象可以实时查看拍照或视频录制效果,设置好尺寸后需要添加到父view的layer中。复制代码

我在参考了网上的很多博客并自己摸索了以后,写了一个具体的实现案例,过程中遇到很多坑,在此记录并分享一下。

运行环境:Xcode 8.3.2 + iOS 8. 4真机、iOS 10.3.1真机

一、核心步骤:

1、创建AVCaptureSession会话 2、创建AVCaptureDevice设备 3、创建输入AVCaptureDeviceInput与输出设备AVCaptureMetadataOutput,并添加到上面的会话中 4、创建预览层 5、设置扫描区域

二、实现

从上面的描述看,除了预览层,其他的和UI界面似乎没什么关系,但是实际开发中,扫描界面一般都是设计的比较人性化的,如支付宝、微信等,中间都有一个小框,有个线上下扫,这个其实就是用UI来配合扫描二维码,给用户一种好的体验。

1、界面布局

2、主要代码

#import "ViewController.h"#import 
@interface ViewController ()
/** * UI */@property (weak, nonatomic) IBOutlet UIView *scanView;@property (weak, nonatomic) IBOutlet UIImageView *scanline;@property (weak, nonatomic) IBOutlet UILabel *result;/** * 扫描区域的高度约束值(宽度一致) */@property (weak, nonatomic) IBOutlet NSLayoutConstraint *scanViewH;/** * 扫描线的顶部约束值 */@property (weak, nonatomic) IBOutlet NSLayoutConstraint *scanlineTop;/** * 扫描线的高度 */@property (weak, nonatomic) IBOutlet NSLayoutConstraint *scanlineH;@property(nonatomic, strong) CALayer *maskLayer;/** * 五个类 */@property(nonatomic, strong) AVCaptureDevice *device;@property(nonatomic, strong) AVCaptureDeviceInput *input;@property(nonatomic, strong) AVCaptureMetadataOutput *output;@property(nonatomic, strong) AVCaptureSession *session;@property(nonatomic, strong) AVCaptureVideoPreviewLayer *layer;@end@implementation ViewController#pragma mark - 懒加载-(AVCaptureDevice *)device{ if (_device == nil) { _device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; } return _device; }-(AVCaptureDeviceInput *)input{ if (_input == nil) { _input = [AVCaptureDeviceInput deviceInputWithDevice:self.device error:nil]; } return _input; }-(AVCaptureMetadataOutput *)output{ if (_output == nil) { _output = [[AVCaptureMetadataOutput alloc]init]; [_output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()]; } return _output;}#pragma mark - ViewController生命周期/** * 执行扫描动画 */-(void)viewDidAppear:(BOOL)animated{ [super viewDidAppear:animated]; [self startAnim]; }/** * 注册进入前台通知 保证下次进来还有扫描动画 */-(void)viewWillAppear:(BOOL)animated{ [super viewWillAppear:animated]; //注册程序进入前台通知 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector (startAnim) name: UIApplicationWillEnterForegroundNotification object:nil];}/** * 移除通知 */-(void)viewWillDisappear:(BOOL)animated{ [super viewWillDisappear:animated]; //解除程序进入前台通知 [[NSNotificationCenter defaultCenter] removeObserver:self name: UIApplicationWillEnterForegroundNotification object:nil]; }- (void)viewDidLoad { [super viewDidLoad]; //1、创建会话 AVCaptureSession *session = [[AVCaptureSession alloc]init]; if ([session canSetSessionPreset:AVCaptureSessionPresetHigh]) { [session setSessionPreset:AVCaptureSessionPresetHigh]; } //2、添加输入和输出设备 if([session canAddInput:self.input]){ [session addInput:self.input]; } if([session canAddOutput:self.output]){ [session addOutput:self.output]; } //3、设置这次扫描的数据类型 self.output.metadataObjectTypes = self.output.availableMetadataObjectTypes; //4、创建预览层 AVCaptureVideoPreviewLayer *layer = [AVCaptureVideoPreviewLayer layerWithSession:session]; layer.frame = self.view.bounds; [self.view.layer insertSublayer:layer atIndex:0]; //5、创建周围的遮罩层 CALayer *maskLayer = [[CALayer alloc]init]; maskLayer.frame = self.view.bounds; //此时设置的颜色就是中间扫描区域最终的颜色 maskLayer.backgroundColor = [UIColor colorWithRed:0.1 green:0.1 blue:0.1 alpha:0.2].CGColor; maskLayer.delegate = self; [self.view.layer insertSublayer:maskLayer above:layer]; //让代理方法调用 将周围的蒙版颜色加深 [maskLayer setNeedsDisplay]; //6、关键设置扫描的区域 方法一:自己计算 // CGFloat x = (self.view.bounds.size.width - self.scanViewH.constant) * 0.5; // // CGFloat y = (self.view.bounds.size.height- self.scanViewH.constant) * 0.5; // // CGFloat w = self.scanViewH.constant; // // CGFloat h = w; // // // self.output.rectOfInterest = CGRectMake(y/self.view.bounds.size.height, x/self.view.bounds.size.width, h/self.view.bounds.size.height, w/self.view.bounds.size.width); //6、关键设置扫描的区域,方法二:直接转换,但是要在 AVCaptureInputPortFormatDescriptionDidChangeNotification 通知里设置,否则 metadataOutputRectOfInterestForRect: 转换方法会返回 (0, 0, 0, 0)。 __weak __typeof(&*self)weakSelf = self; [[NSNotificationCenter defaultCenter] addObserverForName:AVCaptureInputPortFormatDescriptionDidChangeNotification object:nil queue:[NSOperationQueue currentQueue] usingBlock: ^(NSNotification *_Nonnull note) { weakSelf.output.rectOfInterest = [weakSelf.layer metadataOutputRectOfInterestForRect:self.scanView.frame]; }]; //7、开始扫描 [session startRunning]; self.session = session; self.layer = layer; self.maskLayer = maskLayer;}-(void)dealloc{ [[NSNotificationCenter defaultCenter] removeObserver:self]; }#pragma mark - 代理方法/** * 如果扫描到了二维码 回调该代理方法 */- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection{ if(metadataObjects.count > 0 && metadataObjects != nil){ AVMetadataMachineReadableCodeObject *metadataObject = [metadataObjects lastObject]; NSString *result = metadataObject.stringValue; self.result.text = result; [self.session stopRunning]; [self.scanline removeFromSuperview]; } }/** * 蒙版中间一块要空出来 */-(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx{ if (layer == self.maskLayer) { UIGraphicsBeginImageContextWithOptions(self.maskLayer.frame.size, NO, 1.0); //蒙版新颜色 CGContextSetFillColorWithColor(ctx, [UIColor colorWithRed:0.1 green:0.1 blue:0.1 alpha:0.8].CGColor); CGContextFillRect(ctx, self.maskLayer.frame); //转换坐标 CGRect scanFrame = [self.view convertRect:self.scanView.frame fromView:self.scanView.superview]; //空出中间一块 CGContextClearRect(ctx, scanFrame); }}@end#pragma mark - 自定义方法/** * 扫描的那条线动起来 */-(void)startAnim{ //如果是第二次进来 那么动画已经执行完毕 要重新开始动画的话 必须让约束归位 if(self.scanlineTop.constant == self.scanViewH.constant - 4){ self.scanlineTop.constant -= self.scanViewH.constant - 4; [self.view layoutIfNeeded]; } //执行动画 [UIView animateWithDuration:3.0 delay:0 options:UIViewAnimationOptionRepeat animations:^{ self.scanlineTop.constant = self.scanViewH.constant - 4; [self.view layoutIfNeeded]; } completion:nil]; }复制代码

Info.plist中 不同iOS版本需要添加相应的权限

三、最终效果

总结

一、遇到的坑

1、设置了AutoLayout,想要做动画,这时候动画放在viewDidAppear中执行,并且不要用bounds,frame来改变动画,要用具体的约束,但是直接在UIView动画中修改约束是没效果的,需要在设置完约束以后,加上[self.view layoutIfNeeded];

2、设置扫描区域,也就是设置AVCaptureMetadataOutputrectOfInterest属性,它是一个CGRect类型,但是它的四个值和传统的不一样,是**(y,x,高,宽)**且是比例值,取值范围为0~1。那么有两种方案,第一种需要自己计算具体位置的比例,如代码中注释的那些。第二种方案用AVCaptureVideoPreviewLayermetadataOutputRectOfInterestForRect方法,但是直接设置是没有效果的,必须放到通知里,如文中所示。

3、中间方块是通过CALayer两步实现的,第一步设置整个背景颜色,这个颜色根据中间想显示的样式来设置;第二步在代理方法里面重新设置一次背景颜色,这个颜色根据除中间以外的区域来设置,然后将中间的挖掉。但是必须调用setNeedsDisplay方法,否则代理方法不会调用。

二、参考文献

三、

转载于:https://juejin.im/post/5a31139a51882560e356a2f8

你可能感兴趣的文章
Redis命令
查看>>
银行卡二元实名认证
查看>>
图片存储
查看>>
Makefile伪目标
查看>>
《第五章 操作符和表达式》
查看>>
每周一荐:TotalCommand的文件夹同步功能
查看>>
html中表格td的宽度如何设置
查看>>
BZOJ 2457 [BeiJing2011] 双端队列
查看>>
RMI基础篇
查看>>
鼠标悬停在图片时出现×。然后删除图片
查看>>
CH5102 Mobile Service
查看>>
POJ3635 Full Tank
查看>>
XML的两种读取方法
查看>>
jquery插件制作
查看>>
Python简单试题3
查看>>
Effective C++ 笔记
查看>>
hybris 定义 long String
查看>>
【眼见为实】自己动手实践理解数据库READ UNCOMMITED && SERIALIZABLE
查看>>
高并发和大流量解决方案--数据库缓存
查看>>
C# 中类和结构的区别
查看>>