文章目录

  • 前言
  • 正文
    • 1.定位二维码的位置
    • 2.扫码、解析
  • 总结

前言

业务中一直有扫码的需求,这次说需要扫多个码(详细一点是一图多码),有点东西的。
第一点:怎么做:拿到手第一反应是有没有什么库可以直接调用的,不动脑星人检索了一下Zbar和ZXing,ZXing不适合iOS、Zbar也没有一图多码的方法可以直接用的,况且引入库增加代价也高,最后,原生撸吧。
第二点:做成什么样:app有什么对标的,打开支某宝、微某的二维码扫码看看,平时自己也遇见过吧。
扫码:

  • 识别到一个码,跳转对应链接
  • 识别到多个码,显示二维码定位位置,点击定位图跳转

    第三点:划重点,原生扫码的方式很简单,难点在于如何定位到二维码的位置,上菜。

正文

1.定位二维码的位置

思路参考:二维码扫码效果(多个二维码识别和点选)

首先二维码识别原理是三个角标定位的,然后再通过角标读取内部信息;假设扫码layer是整个view,再将识别到的坐标信息转换到view上的坐标,这样就可以得到定位的信息进行绘图标注。

2.扫码、解析

扫码需要放到队列去,否则容易主线程阻塞。相机权限得先请求开启一波。

#import <AVFoundation/AVFoundation.h>
// AVCaptureMetadataOutputObjectsDelegate///主队列
#define GCD_main_queue dispatch_get_main_queue()
#define WeakSelf typeof(self) __weak weakSelf = self;// 输入输出中间桥梁(会话)
@property (strong, nonatomic) AVCaptureSession *session;
// 多个二维码 定位的数组
@property (strong, nonatomic) NSMutableArray *qrCodesArray;
//扫码点选回调
@property (nonatomic, copy) void(^clickQrCodeBlock)(NSString *qrStr);
//多个二维码位置点击按钮数组
@property (strong, nonatomic) NSMutableArray *qrCodesButtonArray;
// 重新扫码
@property (strong, nonatomic) UIButton *reScanButton;- (void)viewDidAppear:(BOOL)animated{[super viewDidAppear:animated];[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {if (granted) {dispatch_async(GCD_main_queue, ^{[self startRunning];// 扫码});}else{dispatch_async(GCD_main_queue, ^{//@"无法访问照相机,请在设置中打开相机权限"});}}];
}

重启扫码的btn,这里可以自由发挥

   self.reScanButton = [UIButton buttonWithType:UIButtonTypeCustom];//重新扫码self.reScanButton.backgroundColor = [UIColor blueColor];self.reScanButton.hidden = YES;[self.reScanButton setTitle:@"重新扫码" forState:UIControlStateNormal];self.reScanButton.backgroundColor = [UIColor blueColor];[self.cameraView addSubview:self.reScanButton];[self.reScanButton addTarget:self action:@selector(reScanBtnAction:) forControlEvents:UIControlEventTouchUpInside];[self.reScanButton mas_makeConstraints:^(MASConstraintMaker *make) {make.centerX.equalTo(self.cameraView);make.bottom.equalTo(self.cameraView.mas_bottom).offset(-60 * FTGetScreenScale());make.size.mas_equalTo(CGSizeMake(200 * FTGetScreenScale(), 60 * FTGetScreenScale()));}];self.qrCodesButtonArray = [NSMutableArray new];

干货:扫码开启和暂停

/**start running capture*/
- (void)startRunning {if(![self.session isRunning]){[self.session startRunning];}if(self.qrCodesButtonArray.count){//移除上一次的标记[self.qrCodesButtonArray enumerateObjectsUsingBlock:^(UIButton *button, NSUInteger idx, BOOL *stop) {[button removeFromSuperview];}];self.qrCodesButtonArray = [NSMutableArray new];self.reScanButton.hidden = YES;}}
/**stop running capture*/
- (void)stopRunning {// reload animation on mainThreadWeakSelfdispatch_async(dispatch_get_main_queue(), ^{[weakSelf.session stopRunning];});
}- (AVCaptureSession *)session {if (!_session) {//1.获取输入设备(摄像头)AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];//2.根据输入设备创建输入对象AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device error:NULL];if (input == nil) {return nil;}//3.创建元数据的输出对象AVCaptureMetadataOutput *output = [[AVCaptureMetadataOutput alloc]init];//4.设置代理监听输出对象输出的数据,在主线程中刷新[output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];// 5.创建会话(桥梁)AVCaptureSession *session = [[AVCaptureSession alloc]init];//实现高质量的输出和摄像,默认值为AVCaptureSessionPresetHigh,可以不写[session setSessionPreset:AVCaptureSessionPresetHigh];// 6.添加输入和输出到会话中(判断session是否已满)if ([session canAddInput:input]) {[session addInput:input];}if ([session canAddOutput:output]) {[session addOutput:output];}// 7.告诉输出对象, 需要输出什么样的数据 (二维码还是条形码等) 要先创建会话才能设置output.metadataObjectTypes = @[AVMetadataObjectTypeQRCode,AVMetadataObjectTypeCode128Code,AVMetadataObjectTypeCode93Code,AVMetadataObjectTypeCode39Code,AVMetadataObjectTypeCode39Mod43Code,AVMetadataObjectTypeEAN8Code,AVMetadataObjectTypeEAN13Code,AVMetadataObjectTypeUPCECode,AVMetadataObjectTypePDF417Code,AVMetadataObjectTypeAztecCode];// 8.创建预览图层AVCaptureVideoPreviewLayer *previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:session];[previewLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];previewLayer.frame = self.cameraView.bounds;[self.cameraView.layer insertSublayer:previewLayer atIndex:0];//9.设置有效扫描区域,默认整个图层(很特别,1、要除以屏幕宽高比例,2、其中x和y、width和height分别互换位置)
//        CGRect rect = CGRectMake(kBgImgY/ScreenHeight, kBgImgX/ScreenWidth, kBgImgWidth/ScreenHeight, kBgImgWidth/ScreenWidth);_session = session;}return _session;
}#pragma mark AVCaptureMetadataOutputObjectsDelegate
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection{if ([metadataObjects count] >0){NSLog(@"========扫描后的url是:==============");NSMutableArray *muchArray = [NSMutableArray new];[metadataObjects enumerateObjectsUsingBlock:^(AVMetadataMachineReadableCodeObject *obj, NSUInteger idx, BOOL *stop) {if ([obj.type isEqualToString:AVMetadataObjectTypeQRCode]) {   //判断是否有数据,是否是二维码数据[muchArray addObject:obj];}}];[self stopRunning];// 我这里是扫码到结果就暂停扫码、加个重新扫描btn会用户友好一点,后面是处理扫码结果if([muchArray count] == 1){//扫描到一个二维码信息AVMetadataMachineReadableCodeObject * metadataObject = [muchArray objectAtIndex:0];NSString *stringValue = metadataObject.stringValue;self.urlString = stringValue.length?stringValue:@"";NSLog(@" 1111扫描后的url是:%@",self.urlString);if(self.urlString.length){dispatch_async(dispatch_get_main_queue(), ^{[self analyseResultAry:self.urlString];self.reScanButton.hidden = NO;});}}else if([muchArray count] >1){// 多个二维码信息 显示二维码定位页面  选择跳转self.qrCodesArray = [NSMutableArray new];[muchArray enumerateObjectsUsingBlock:^(AVMetadataMachineReadableCodeObject *result, NSUInteger idx, BOOL *stop) {NSMutableDictionary *dic = [NSMutableDictionary new];NSString *code = result.stringValue;[dic setObject:code forKey:@"code"];NSLog(@"2222 扫描后的url是:%@",code);// 标注多个二维码CGRect frame = [self makeFrameWithCodeObject:result Index:self.qrCodesArray.count];NSString *frameStr = NSStringFromCGRect(frame);[dic setObject:frameStr forKey:@"frame"];[self.qrCodesArray addObject:dic];//记录下标注的数组,下次扫码移除前面的标注}];dispatch_async(dispatch_get_main_queue(), ^{self.reScanButton.hidden = NO;});}}
}
//选择多个二维码中的一个
- (void)handleBtnAction:(UIButton *)sender {NSInteger index = sender.tag - 1000;if (index < self.qrCodesArray.count) {NSDictionary *dic = self.qrCodesArray[index];if([dic.allKeys containsObject:@"code"]){self.urlString = [dic objectForKey:@"code"]?[dic objectForKey:@"code"]:@"";NSLog(@"2222 扫描后的url是: 选中 %@",self.urlString);if(self.urlString.length){[self analyseResultAry:self.urlString];}}}
}//重新扫码
-(void)reScanBtnAction:(UIButton *)sender {[self startRunning];
}/*AVMetadataMachineReadableCodeObject,输出的点位坐标是其在原始数据流上的坐标,与屏幕视图坐标不一样,(坐标系,值都会有差别)将坐标值转为屏幕显示的图像视图(self.videoPreviewLayer)上的坐标值*/
-(CGRect)makeFrameWithCodeObject:(AVMetadataMachineReadableCodeObject *)objc Index:(NSInteger)index
{//将二维码坐标转化为扫码控件输出视图上的坐标//     CGSize isize = CGSizeMake(720.0, 1280.0); // 尺寸可以考虑不要写死,当前设置的是captureSession.sessionPreset = AVCaptureSessionPreset1280x720;CGSize isize = self.cameraView.frame.size; //扫码控件的输出尺寸,float Wout = 0.00;float Hout = 0.00;BOOL wMore = YES;/*取分辨率与输出的layer尺寸差,此处以AVLayerVideoGravityResizeAspectFill填充方式为例,判断扫描的范围更宽还是更长,并计算出超出部分的尺寸,后续计算减去这部分。如果是其它填充方式,计算方式不一样(比如AVLayerVideoGravityResizeAspect,则计算计算留白的尺寸,并后续补足这部分)*/if (isize.width/isize.height > self.cameraView.bounds.size.width/self.cameraView.bounds.size.height) {//当更宽时,计算扫描的坐标x为0 的点比输出视图的0点差多少(输出视图为全屏时,即屏幕外有多少)wMore = YES;Wout = (isize.width/isize.height)* self.cameraView.bounds.size.height;Wout = Wout - self.cameraView.bounds.size.width;Wout = Wout/2;}else{// 当更长时,计算y轴超出多少。wMore = NO;Hout = (isize.height/isize.width)* self.cameraView.bounds.size.width;Hout = Hout  - self.cameraView.bounds.size.height;Hout = Hout/2;}CGPoint point1 = CGPointZero;CGPoint point2 = CGPointZero;CGPoint point3 = CGPointZero;CGPoint point4 = CGPointZero;/*源坐标系下frame和角点,都是比例值,即源视频流尺寸下的百分比值。例子:frame :(x = 0.26720550656318665, y = 0.0014114481164142489), size = (width = 0.16406852006912231, height = 0.29584407806396484))objc.corners:{0.26823519751360592, 0.29203594744002659}{0.4312740177700658, 0.29725551905635411}{0.4294213439632073, 0.012761536345436197}{0.26720551457151021, 0.0014114481640513654}*/CGRect frame = objc.bounds;//在源坐标系的frame,NSArray *array = objc.corners;//源坐标系下二维码的角点CGPoint P = frame.origin;CGSize S = frame.size;//获取点for (int n = 0; n< array.count; n++) {CGPoint point = CGPointZero;CFDictionaryRef dict = (__bridge CFDictionaryRef)(array[n]);CGPointMakeWithDictionaryRepresentation(dict, &point);
//        NSLog(@"二维码角点%@",NSStringFromCGPoint(point));//交换xy轴point.x = point.y +  point.x;point.y = point.x - point.y;point.x = point.x - point.y;//x轴反转point.x = (1-point.x);//point乘以比列。减去尺寸差,if (wMore) {point.x = (point.x * (isize.width/isize.height)* self.cameraView.bounds.size.height) - Wout;point.y = self.cameraView.bounds.size.height *(point.y);}else{point.x = self.cameraView.bounds.size.width *(point.x);point.y = (point.y) * (isize.height/isize.width)* self.cameraView.bounds.size.width - Hout;}if (n == 0) {point1 = point;}if (n == 1) {point2 = point;}if (n == 2) {point3 = point;}if (n == 3) {point4 = point;}}//通过获取最小和最大的X,Y值,二维码在视图上的frame(前面得到的点不一定是正方形的二维码,也可能是菱形的或者有一定旋转角度的)float minX = point1.x;minX = minX>point2.x?point2.x:minX;minX = minX>point3.x?point3.x:minX;minX = minX>point4.x?point4.x:minX;float minY = point1.y;minY = minY>point2.y?point2.y:minY;minY = minY>point3.y?point3.y:minY;minY = minY>point4.y?point4.y:minY;P.x = minX;P.y = minY;float maxX = point1.x;maxX = maxX<point2.x?point2.x:maxX;maxX = maxX<point3.x?point3.x:maxX;maxX = maxX<point4.x?point4.x:maxX;float maxY = point1.y;maxY = maxY<point2.y?point2.y:maxY;maxY = maxY<point3.y?point3.y:maxY;maxY = maxY<point4.y?point4.y:maxY;S.width = maxX - minX;S.height = maxY - minY;//y轴坐标方向调整CGRect QRFrame = CGRectMake(P.x , P.y  , S.width, S.height);UIButton *tempButton = [UIButton buttonWithType:UIButtonTypeCustom];//多个二维码添加选择btntempButton.backgroundColor = [UIColor blueColor];tempButton.frame = QRFrame;[self.cameraView addSubview:tempButton];tempButton.tag = 1000 + index;[tempButton addTarget:self action:@selector(handleBtnAction:) forControlEvents:UIControlEventTouchUpInside];[self.qrCodesButtonArray addObject:tempButton];return QRFrame;
}-(void)analyseResultAry:(NSString *)resultAsString{WeakSelf[[AFNetworkReachabilityManager sharedManager] startMonitoring];[[AFNetworkReachabilityManager sharedManager] setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {[[AFNetworkReachabilityManager sharedManager] stopMonitoring];if (status >= 1) {// 有网络if (resultAsString.length) {//  结果干点啥~~~~												

iOS扫码一图多码原生处理AVCaptureSession相关推荐

  1. 微信扫码充值 php,PHP原生微信扫码支付

    素材火分享了多个微信支付源码,有用户需要一款PHP原生代码写的微信扫码支付,不基于任何框架,完全手写.需要其他支付源码的可找素材火管理员定制开发. 下载资源 下载积分: 800 积分 扫码支付只要授权 ...

  2. iOS扫码识别【自动拉近放大】(扫描较小二维码地过程中拉近放大图片)【光线暗的时候,能够自动打开闪光灯】

    文章目录 前言 I.搭建一个统一识别二维码的控制器 II.工具方法 QRCodeUtil see also AVMetadataObjectType 前言 先回顾下AVFoundation的扫码原理图 ...

  3. 一人之下ios扫码_一人之下ios版

    一人之下ios版是一款同名动漫改编格斗游戏,横版格斗行云流水的连击,解开未知的支线番外,感兴趣的玩家快来下载体验吧! 一人之下ios版游戏简介: <一人之下>同名动漫改编.异能格斗国潮手游 ...

  4. 一人之下ios扫码_一人之下手游ios版下载

    一人之下IOS版是一款3D动作RPG,继承了原作的画风,并穿插入动漫剧情,更是有着全程的中文配音,而在战斗系统上,游戏除了普通的格斗外,还有着丰富的职业和连招技能,更有着非常考验玩家反应能力的QTE系 ...

  5. 一人之下ios扫码_一人之下ios

    一人之下ios是一款由漫画改编而成的格斗类游戏.在游戏中,真实还原了剧情,以及人物特点,玩家自由选择角色人物,开启一段冒险之旅了,去挑战游戏中的关卡了.加上优美的音乐,会陶醉于游戏中,快来享受吧. 一 ...

  6. Flutter 最佳扫码插件

    插件已更新,支持自定义视图,具体请查看<Flutter 最佳扫码插件--自定义视图> 文章目录 扫码 用法 配置权限 iOS 权限请求 调用API 例子 TODO 插件开发 欢迎关注公众号 ...

  7. java实现简单扫码登录功能(模仿微信网页版扫码)

    java实现简单扫码登录功能 模仿微信pc网页版扫码登录 使用js代码生成qrcode二维码减轻服务器压力 js循环请求服务端,判断是否qrcode被扫 二维码超时失效功能 二维码被扫成功登录,服务端 ...

  8. iOS原生二维码扫码实现(含蒙版和扫码动画)

    #一.iOS实现原生扫码的意义 二维码扫码功能对于现在的iOS App开发来说是非常重要的. 通常为了节省开发时间,很多开发者会采用ZXing和ZBar等第三方SDK进行开发. 这样的好处是快速便捷, ...

  9. 用ios企业证书发布ipa到服务器上扫码下载

    这段时间公司需要做一个ios的app,用的是mui框架,在HBuilder中发行为原生的安装包,用的ios企业证书.我从ipa包生成之后说起. ipa包生成之后,就到了下载这一步了,因为是企业证书,上 ...

最新文章

  1. 埃森哲5G智慧城市报告:美国GDP将狂增5千亿
  2. html5中页面拨打电话的方式
  3. Boost:bind绑定__cdecl(成员函数)测试程序
  4. JavaEE6入门02—Myeclipse8.5+GlassFish
  5. 如何执行一段java代码_V8 之 如何执行一段 JavaSscript 代码
  6. Java 异步回调机制实例解析
  7. 全国行政区划代码/全国城市代码
  8. Java Web开发实战经典(基础篇)
  9. windows系统常见端口和木马默认使用端口
  10. 【无捆绑,可修改主页】系统之家 win7 32位64位极度纯净版系统(每月更新)
  11. 关于antd组件 datePicker和moment出现英文的坑
  12. java编程思想学习笔记——21多线程
  13. go build cannot find package 问题
  14. 技术接受模型(TAM,Technology Acceptance Model)
  15. See-SQL审计平台介绍及部署
  16. 抖音直播行为规范考试
  17. dashboard的安装使用
  18. 汽车加油问题--贪心算法
  19. java实现第四届蓝桥杯猜灯谜
  20. Tornado+Pyecharts+LayUI搭建个人足迹地图服务

热门文章

  1. v-for在表格标签的tr标签里不能用
  2. 华为路由交换机端口的三种模式
  3. 斜率优化---感谢此文让我彻底弄懂斜率优化
  4. php将证书保存位pem格式,openssl-如何以.pem格式保存证书中的公钥
  5. ubuntu的常用命令--Day1
  6. Android_AsyncTask学习
  7. 水利防汛之水库和河道预警SQL语句
  8. python中时间相加问题
  9. 收到包含无效cookie的cookie头[1597904809; JSESSIONID=410E5AB8E2E97F02AB95C8C77A896ECA]。将忽略该cookie。?注意:此错误的进一步
  10. 为什么L1惩罚L2惩罚更容易得到稀疏解