#一、iOS实现原生扫码的意义

二维码扫码功能对于现在的iOS App开发来说是非常重要的。
通常为了节省开发时间,很多开发者会采用ZXing和ZBar等第三方SDK进行开发。
这样的好处是快速便捷,但是缺点也是在于如果要自定义一部分功能,可能对源码进行第二次开发来说比较辛苦。
如果采用的其他开发者提供的进一步封装的扫码功能,又有可能因为编码风格或者缺少注释变得晦涩难懂。
所以掌握原生二维码扫码是非常重要的,优点也是非常明显:
1、扫码识别效率高
2、使用灵活,自定义UI方便
3、便于维护


#二、核心功能介绍

##1、扫码功能:

原生扫码的功能主要采用的是AVFoundation类库下的AVCaptureMetadataOutput,这个输出类可以实现对二维码,条形码等等二维码的的直接识别。
需要注意的是,如果想用开发Mac版本的二维码扫码功能, AVCaptureMetadataOutput是不能实现的,这个类只支持iOS的扫码功能。
如果想要实现Mac上的二维码扫码功能可以参考我的另一篇文章:《基于MacOSX平台下的二维码扫码功能》
链接地址:http://blog.csdn.net/super_dl_csdn/article/details/76460745
具体的其他注意事项和细节,将会在后续的代码部分进行解释。
##2、蒙版功能:
对于二维码扫码来说,实现一个黑色的半透明蒙版,并且限定扫描区域是十分有必要的。
这对于引导用户正确的扫码,提高用户体验度和扫码效率是非常有用的。
但是这个功能并非核心功能,而且需要配合后面的限定扫描区域进一步使用,如果并非必要,可以不作为主要的功能点。
##3、限定扫描区域和动画功能:
如果不限行扫码的区域,对于启动扫码后的视频采集将会以全部屏幕的形势展现出来,这是不合理的。
限定一个扫描区域也有利于用户有目的将二维码放置于合适的位置,也有利于提高识别的效率。
添加扫码动画和适当的进行UI修饰,可以提高用户体验度,同时可是实时告知用户扫码功能是否启用。


#三、项目核心代码实现

整个代码分为两个大类:视图控制器部分代码和扫码类功能代码。具体的源码将会在后续给出链接,项目的结构如下:

  • ViewController:利用StoryBoard生成的根视图控制器,用于跳转扫码界面。
  • QRScanForIOS:二维码扫码类包,用于二维码扫码功能的实现,蒙版图层和扫描动画的封装。
  • QRManager:二维码扫码功能类,利用AVFoundation实现原生扫码功能
  • QMaskView:创建半透明的黑色蒙版视图
  • QRScanAnimationView:二维码扫码限制区域的界面设置和动画封装
  • QRScanForIOSHeader:类包头文件,包含该头文件即可使用相关功能
  • QRViewController:二维码扫码视图控制器,用于展现扫码功能界面

##1、扫码功能实现

1)扫码头文件功能的实现:

//返回类型的block
typedef void(^finishBlock)(BOOL finish,NSError * error);/**初始化扫码Manange@param delegate 代理@param block 返回Block*/
- (void)initQrManagerWithDelegateL:(id)delegatefinishInitBlock:(finishBlock)block;/**设置扫码区域的相关参数@param supView 父视图@param viewFrame 扫码区域的大小*/
- (void)setPreviewLayerWithSupview:(UIView *)supViewwithViewFrame:(CGRect)viewFrame;//开始扫描
- (void)startScan;//停止扫描
- (void)stopScan;
  1. 扫码功能实例方法的实现:

@interface QRManager ()@property (nonatomic,strong) AVCaptureSession *session;
@property (nonatomic,strong) AVCaptureMetadataOutput *metadataOutput;
@end/**初始化扫码Manange@param delegate 代理@param block 返回Block*/
- (void)initQrManagerWithDelegateL:(id)delegatefinishInitBlock:(finishBlock)block
{//创建上下文启动器_session = [[AVCaptureSession alloc] init];//设置DeviceAVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];NSError *error;AVCaptureDeviceInput *deviceInput = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];if (deviceInput) {//理论上这里应该添加一个判断看看能不能添加input[_session addInput:deviceInput];//创建一个输出类型self.metadataOutput = [[AVCaptureMetadataOutput alloc] init];//设置代理,扫描结果将会利用代理返回[self.metadataOutput setMetadataObjectsDelegate:delegate queue:dispatch_get_main_queue()];// 这行代码要在设置 metadataObjectTypes 前[_session addOutput:self.metadataOutput]; // 设置了识别类型为QRCode,枚举还有其他乐行可以进一步添加self.metadataOutput.metadataObjectTypes = @[AVMetadataObjectTypeQRCode];//设置成功了返回YESblock(YES,nil);}else{NSLog(@"%@", error);//失败,打印Errorblock(NO,error);}}
/**设置扫码区域的相关参数@param supView 父视图@param viewFrame 扫码区域的大小*/
- (void)setPreviewLayerWithSupview:(UIView *)supViewwithViewFrame:(CGRect)viewFrame
{//设置扫码区域AVCaptureVideoPreviewLayer *previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:_session];previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;//默认的是全部的界面,这样整个背景都是摄像头信息previewLayer.frame = CGRectMake(0, 84, supView.frame.size.width, supView.frame.size.height-84);[supView.layer insertSublayer:previewLayer atIndex:0];//重点1:采集到的摄像头很多情况是顺时针旋转90度的,造成这个的原因可能是AVFoundation本身的原因,需要进行下面步骤的修正://1、获取到AVCaptureVideoPreviewLayer的方向并进行修正AVCaptureConnection *previewLayerConnection=previewLayer.connection;//2、判断并修正if ([previewLayerConnection isVideoOrientationSupported]){[previewLayerConnection setVideoOrientation:[[UIApplication sharedApplication] statusBarOrientation]];}//重点2:全屏幕扫码不是我们需要想要的功能,我们需要限制扫码的范围就要使用rectOfInterest。//然而这个属性的frame是一个比例系数,范围是[0,1];很多人采用试的方式是不合理的,利用metadataOutputRectOfInterestForRect方法可以成功的将AVCaptureVideoPreviewLayer所在的父视图中的Frame转换成对应的rectOfInterest的frame。//1、将传进来的需要限制区域的viewFrame转换成AVCaptureVideoPreviewLayer需要的FrameCGRect intertRect = [previewLayer metadataOutputRectOfInterestForRect:viewFrame];//2、设置限定区域self.metadataOutput.rectOfInterest = intertRect;}- (void)startScan
{[self.session startRunning];
}- (void)stopScan
{[self.session stopRunning];
}

##2、蒙版的制作

蒙版的制作本项目采用的是创建一个继承自UIView的子类,然后利用贝塞尔曲线实现,头文件的内容如下:

**根据蒙版的大小,视图扫码区域的大小创建蒙版@param maskFrame 蒙版在父视图中的大小@param scanFrame 扫码区域的大小@return 返回蒙版View*/
- (instancetype)initMaskViewWithFrame:(CGRect)maskFramewithScanFrame:(CGRect)scanFrame;

view的.m文件中实现如下:

/**根据蒙版的大小,视图扫码区域的大小创建蒙版@param maskFrame 蒙版在父视图中的大小@param scanFrame 扫码区域的大小@return 返回蒙版View*/
- (instancetype)initMaskViewWithFrame:(CGRect)maskFramewithScanFrame:(CGRect)scanFrame
{self = [super initWithFrame:maskFrame];if (self) {self.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.5];UIBezierPath *maskPath = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];[maskPath appendPath:[[UIBezierPath bezierPathWithRoundedRect:scanFrame cornerRadius:1] bezierPathByReversingPath]];CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];maskLayer.path = maskPath.CGPath;self.layer.mask = maskLayer;}return self;
}

##3、限制框的设置和动画制作

这里仅仅是举了一个例子,采用一个定时器去执行动画,当然有更好的方法。
头文件的部分代码如下:

/**开始动画*/
- (void)startAnimation;/**结束动画*/
- (void)stopAnimation;

动画类的.m文件实现如下:

@interface QRScanAnimationView ()
{UIImageView * line_imageView_ ;NSTimer * animation_timer_;int addOrCut_;
}@end- (instancetype)initWithFrame:(CGRect)frame
{self = [super initWithFrame:frame];if (self) {//使用CGContextRef重写drawRect方法会产生一个默认的黑色的北京,需要在初始化方法中提前设置为clearcolor[self setBackgroundColor:[UIColor clearColor]];//线移动的imageViewline_imageView_=[[UIImageView alloc]initWithImage:[UIImage imageNamed:@"qrcode_Scan_weixin_Line@2x.png"]];[self addSubview:line_imageView_];//初始位置为当前视图距离顶部的四分之一处[line_imageView_ setFrame:CGRectMake(0, self.bounds.size.height/4, self.bounds.size.width, 20)];}return self;
}// 覆盖drawRect方法,你可以在此自定义绘画和动画
- (void)drawRect:(CGRect)rect
{//An opaque type that represents a Quartz 2D drawing environment.//一个不透明类型的Quartz 2D绘画环境,相当于一个画布,你可以在上面任意绘画CGFloat weight_ = self.frame.size.width;        //视图宽度CGFloat height_ = self.frame.size.height;       //视图高度CGFloat view_height_ = 10;CGFloat view_weight_ = 10;                      //纵向线段宽度CGFloat view_long_ = weight_/10;                //线段长度CGContextRef context = UIGraphicsGetCurrentContext();CGContextSetRGBFillColor (context,  0, 1, 0, 1.0);//设置填充颜色CGContextSetRGBStrokeColor(context,0, 1, 0, 1.0);//画笔线的颜色CGContextSetLineWidth(context, view_height_);//上,左,顶CGPoint aPoints[2];//坐标点aPoints[0] =CGPointMake(0, view_height_/2);//坐标1aPoints[1] =CGPointMake(view_long_, view_height_/2);//坐标2CGContextAddLines(context, aPoints, 2);//添加线CGContextDrawPath(context, kCGPathStroke); //根据坐标绘制路径//上,左,左aPoints[0] =CGPointMake(view_weight_/2, 0);//坐标1aPoints[1] =CGPointMake(view_weight_/2 , view_long_);//坐标2CGContextAddLines(context, aPoints, 2);//添加线CGContextDrawPath(context, kCGPathStroke); //根据坐标绘制路径//上,右,顶aPoints[0] =CGPointMake(weight_-view_long_,view_height_/2);//坐标1aPoints[1] =CGPointMake(weight_, view_height_/2);//坐标2CGContextAddLines(context, aPoints, 2);//添加线CGContextDrawPath(context, kCGPathStroke); //根据坐标绘制路径//上,右,右aPoints[0] =CGPointMake(weight_-view_weight_/2, 0);//坐标1aPoints[1] =CGPointMake(weight_-view_weight_/2 , view_long_);//坐标2CGContextAddLines(context, aPoints, 2);//添加线CGContextDrawPath(context, kCGPathStroke); //根据坐标绘制路径//下,左,左aPoints[0] =CGPointMake(view_weight_/2, height_-view_long_);//坐标1aPoints[1] =CGPointMake(view_weight_/2 , height_);//坐标2CGContextAddLines(context, aPoints, 2);//添加线CGContextDrawPath(context, kCGPathStroke); //根据坐标绘制路径//下,左,底aPoints[0] =CGPointMake(0, height_-view_height_/2);//坐标1aPoints[1] =CGPointMake(view_long_ , height_-view_height_/2);//坐标2CGContextAddLines(context, aPoints, 2);//添加线CGContextDrawPath(context, kCGPathStroke); //根据坐标绘制路径//下,右,右aPoints[0] =CGPointMake(weight_-view_weight_/2, height_-view_long_);//坐标1aPoints[1] =CGPointMake(weight_-view_weight_/2 , height_);//坐标2CGContextAddLines(context, aPoints, 2);//添加线CGContextDrawPath(context, kCGPathStroke); //根据坐标绘制路径//下,右,底aPoints[0] =CGPointMake(weight_-view_long_, height_-view_height_/2);//坐标1aPoints[1] =CGPointMake(weight_ , height_-view_height_/2);//坐标2CGContextAddLines(context, aPoints, 2);//添加线CGContextDrawPath(context, kCGPathStroke); //根据坐标绘制路径
}/**开始动画*/
- (void)startAnimation
{if (animation_timer_) {[animation_timer_ invalidate];}//创建一个定时器,这种创建方式需要手动将timer放到runloop中animation_timer_=[NSTimer timerWithTimeInterval:0.01 repeats:YES block:^(NSTimer * _Nonnull timer) {if (line_imageView_.frame.origin.y>=self.frame.size.height*3/4) {addOrCut_=-1;}else if (line_imageView_.frame.origin.y<=self.frame.size.height/4){addOrCut_=1;}[line_imageView_ setFrame:CGRectMake(line_imageView_.frame.origin.x, line_imageView_.frame.origin.y+addOrCut_, line_imageView_.frame.size.width, line_imageView_.frame.size.height)];}];[[NSRunLoop mainRunLoop]addTimer:animation_timer_ forMode:NSDefaultRunLoopMode];}
- (void)stopAnimation
{[animation_timer_ invalidate];
}

##4、QRViewController中的设置和调用

在需要调用扫码功能的界面中,可以进行一下设置就可以实现扫码功能。

//这里采用了Masonry自动布局
#import "Masonry.h"@interface QRViewController ()
{CGRect scan_frame_;QRManager * qr_scan_manager_; //扫码控制中心                      QRMaskView * qr_mask_view_;  //顶部蒙版视图                       QRScanAnimationView * qr_scan_animation_view_; //扫码动画视图     }
- (void)viewDidLoad {[super viewDidLoad];self.view.backgroundColor=[UIColor whiteColor];//首先创建限制区域的大小和frame,如果需要修改限制区域的位置,只需要修改此处的frame即可qr_scan_animation_view_=[[QRScanAnimationView alloc]initWithFrame:                     CGRectMake(self.view.center.x-self.view.frame.size.height/4,                                    self.view.center.y-self.view.frame.size.height/4,                                    self.view.frame.size.height/2,                                      self.view.frame.size.height/2)];[self.view addSubview:qr_scan_animation_view_];//这个是用来设置扫描区域的frame的,这个frame需要注意的是,必须是AVCaptureVideoPreviewLayer所在的layer上的frame,在当前软件中,因为顶部有一个topview,所以应该要在原有的self.view.center.y的基础上上移20+64个像素scan_frame_ = CGRectMake(qr_scan_animation_view_.frame.origin.x,
qr_scan_animation_view_.frame.origin.y-84,
qr_scan_animation_view_.bounds.size.height,
qr_scan_animation_view_.bounds.size.height) ;//创建蒙版的Viewqr_mask_view_=[[QRMaskView alloc]initMaskViewWithFrame:CGRectMake(0, 84, self.view.frame.size.width, self.view.frame.size.height - 84) withScanFrame:scan_frame_];[self.view addSubview:qr_mask_view_];//初始化二维码扫码功能模块单利qr_scan_manager_ = [QRManager sharedManager];//设置相关参数[qr_scan_manager_ initQrManagerWithDelegateL:self finishInitBlock:^(BOOL finish, NSError *error) {if (finish) {//开启成功,开始设置扫码参数并开始进行扫码[qr_scan_manager_ setPreviewLayerWithSupview:self.view withViewFrame:scan_frame_];}else{NSString * qr_error_string_  =[NSString stringWithFormat:@"错误信息:%@",error];UIAlertController * alertController=[UIAlertController alertControllerWithTitle:@"扫描启动失败!" message:qr_error_string_ preferredStyle:UIAlertControllerStyleAlert];UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleCancel handler:nil];[alertController addAction:cancelAction];[self presentViewController:alertController animated:YES completion:nil];}}];
}
- (void)viewWillAppear:(BOOL)animated
{[super viewWillAppear:animated];//界面进入后需要开始进行扫码[qr_scan_manager_ startScan];//开启动画[qr_scan_animation_view_ startAnimation];}
- (void)viewWillDisappear:(BOOL)animated
{[super viewWillDisappear: animated];//界面退出前停止扫码[qr_scan_manager_ stopScan];//停止动画[qr_scan_animation_view_ stopAnimation];
}
pragma mark - AVCaptureMetadataOutputObjectsDelegate
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection
{// id 类型不能点语法,所以要先去取出数组中对象AVMetadataMachineReadableCodeObject *object = [metadataObjects lastObject];if (object == nil) return;if ([object.type isEqualToString:AVMetadataObjectTypeQRCode] ){NSLog(@"得到的qr字符串为:%@",object.stringValue);//进一步的操作// .....}
}

#四、总结

以上是基本的程序设计思路,下面附上代码的链接。
代码可以直接下载下来使用。
https://e.coding.net/SupDongLei/QRScanForIOS.git

PS:感觉自己和二维码比较有缘2333333(手动滑稽)

iOS原生二维码扫码实现(含蒙版和扫码动画)相关推荐

  1. iOS原生二维码扫描(一)

    首先搭建一个最初步的能识别出二维码信息的最基本框架: @interface ScanCodeViewController ()<AVCaptureMetadataOutputObjectsDel ...

  2. iOS 原生二维码扫描和生成

    代码地址如下: http://www.demodashi.com/demo/12551.html 一.效果预览: 功能描述:WSLNativeScanTool是在利用原生API的条件下封装的二维码扫描 ...

  3. IOS 原生二维码、条形码扫描for IOS7 (八)

    转自:http://blog.csdn.net/baidu_31071595/article/details/50878410 同类型文章:http://www.2cto.com/kf/201603/ ...

  4. iOS 原生二维码扫描(可限制扫描区域)

    废话不多说,直接上代码 **先声明几个类** @interface QRCodeController ()<AVCaptureMetadataOutputObjectsDelegate>@ ...

  5. iOS 花式二维码生成和二维码识别

    iOS 原生的二维码识别非常之棒,反正比 ZXing 和 ZBar 效果都好些,所以以后打算尽量用原生的二维码识别,然后最近把原生的二维码生成也顺便做了一遍,并且在原有基础上加了一些样式参数,封了一个 ...

  6. iOS 7原生二维码扫描中文gbk编码乱码的解决

    有的二维码生成的含有中文的数据编码是GBK编码,如百度二维码生成器,使用系统原生二维码扫描就会出现乱码,于是开始网上查阅,该试的方法都尝试过了,终于功夫不负有心人,问题得到了解决,先上代码 NSStr ...

  7. iOS开发-二维码扫描和应用跳转

    iOS开发-二维码扫描和应用跳转   序言 前面我们已经调到过怎么制作二维码,在我们能够生成二维码之后,如何对二维码进行扫描呢? 在iOS7之前,大部分应用中使用的二维码扫描是第三方的扫描框架,例如Z ...

  8. iOS--AVFoundation原生二维码与一维码扫描

    概述 实现二维码和条形码扫描,两大开源组件ZBar与ZXing ZBar: 扫描灵敏性,内存较优,但"圆角二维码"扫描比较困难. ZXing: Google Code上的一个开源的 ...

  9. iOS上二维码和一维码识别系列一

    这段时间在做iOS上二维码和一维码的识别 其间遇到问题无数, 特此总结下来, 希望能给后来的同学供参考 在iOS上, 做二维码识别, 首先在网上查找资料, 查找到ZBar和ZXing这两个开源的识别程 ...

最新文章

  1. mysql 写binlog 原理_MySQL binlog原理及应用
  2. CVPR 2018 目标跟踪相关论文
  3. 分子生物学-肽和多肽
  4. FPGA基础之锁存器与触发器的设计
  5. python read函数菜鸟_关于python的菜鸟问题
  6. muduo网络库学习(三)定时器TimerQueue的设计
  7. java常用类总结_java——常用类的总结
  8. mysqlmodify_modify与change的区别
  9. python去干扰线_GitHub - Guardiant/VerifyCode: 验证码去干扰线识别
  10. Spark RDD Cache Checkpoint
  11. vs2002 vs2003 可能存在的问题以及解决办法!
  12. hdu1215七夕节
  13. 简单的HTML5音乐播放器带歌词滚动,基于jQuery实现歌词滚动版音乐播放器的代码...
  14. Pycharm中文字体变成繁体解决方法
  15. Windows安装Android软件,win7系统安装安卓软件WindowsAndroid的方法
  16. tumblr_如何制作私人Tumblr博客
  17. CCF中学生计算机程序设计入门篇练习2.4.1(NOI1001 温度转换) pascal
  18. hbuilderx升级3.6.5版本后运行到手机端同步资源失败,未得到同步资源的授权,请停止运行后重新运行,并注意手机上的授权提示
  19. WeGeek Talk | 美团外卖
  20. vivo手机如何使用非官方手机主题

热门文章

  1. 如何开好迭代回顾会议(1)目的、议程
  2. CodeWar(JavaScript)---Vowel Count
  3. centos下svn分组权限管理
  4. 《猜画小歌》背后趣事
  5. rachel zhang_用Rachel Andrew为分页媒体翻译CSS
  6. xctf warmup SQL注入
  7. 目前最好用的云存储?
  8. Scala中reduce用法
  9. 大数据业务:数据沉淀、数据挖掘和数据可视化
  10. exercism——入门教程