iOS扫码一图多码原生处理AVCaptureSession
文章目录
- 前言
- 正文
- 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相关推荐
- 微信扫码充值 php,PHP原生微信扫码支付
素材火分享了多个微信支付源码,有用户需要一款PHP原生代码写的微信扫码支付,不基于任何框架,完全手写.需要其他支付源码的可找素材火管理员定制开发. 下载资源 下载积分: 800 积分 扫码支付只要授权 ...
- iOS扫码识别【自动拉近放大】(扫描较小二维码地过程中拉近放大图片)【光线暗的时候,能够自动打开闪光灯】
文章目录 前言 I.搭建一个统一识别二维码的控制器 II.工具方法 QRCodeUtil see also AVMetadataObjectType 前言 先回顾下AVFoundation的扫码原理图 ...
- 一人之下ios扫码_一人之下ios版
一人之下ios版是一款同名动漫改编格斗游戏,横版格斗行云流水的连击,解开未知的支线番外,感兴趣的玩家快来下载体验吧! 一人之下ios版游戏简介: <一人之下>同名动漫改编.异能格斗国潮手游 ...
- 一人之下ios扫码_一人之下手游ios版下载
一人之下IOS版是一款3D动作RPG,继承了原作的画风,并穿插入动漫剧情,更是有着全程的中文配音,而在战斗系统上,游戏除了普通的格斗外,还有着丰富的职业和连招技能,更有着非常考验玩家反应能力的QTE系 ...
- 一人之下ios扫码_一人之下ios
一人之下ios是一款由漫画改编而成的格斗类游戏.在游戏中,真实还原了剧情,以及人物特点,玩家自由选择角色人物,开启一段冒险之旅了,去挑战游戏中的关卡了.加上优美的音乐,会陶醉于游戏中,快来享受吧. 一 ...
- Flutter 最佳扫码插件
插件已更新,支持自定义视图,具体请查看<Flutter 最佳扫码插件--自定义视图> 文章目录 扫码 用法 配置权限 iOS 权限请求 调用API 例子 TODO 插件开发 欢迎关注公众号 ...
- java实现简单扫码登录功能(模仿微信网页版扫码)
java实现简单扫码登录功能 模仿微信pc网页版扫码登录 使用js代码生成qrcode二维码减轻服务器压力 js循环请求服务端,判断是否qrcode被扫 二维码超时失效功能 二维码被扫成功登录,服务端 ...
- iOS原生二维码扫码实现(含蒙版和扫码动画)
#一.iOS实现原生扫码的意义 二维码扫码功能对于现在的iOS App开发来说是非常重要的. 通常为了节省开发时间,很多开发者会采用ZXing和ZBar等第三方SDK进行开发. 这样的好处是快速便捷, ...
- 用ios企业证书发布ipa到服务器上扫码下载
这段时间公司需要做一个ios的app,用的是mui框架,在HBuilder中发行为原生的安装包,用的ios企业证书.我从ipa包生成之后说起. ipa包生成之后,就到了下载这一步了,因为是企业证书,上 ...
最新文章
- OpenCV实现遍历文件夹下所有文件
- ViewVC 1.1.16 发布,CVS/SVN的Web接口
- Redis进阶-Redis集群 【高可用切换】【cluster-require-full-coverage】集群是否完整才能对外提供服务
- 使用typedef声明新类型及函数指针
- 数据结构+算法——错题总结
- 使用git提交项目到码云
- Openfire3.9.3源代码导入eclipse中开发配置指南(转载)
- HTML 5 aside 标签
- Android 基本控件使用
- Java实现带发音的简易电子词典
- redmibook pro 14 arch linux alsamixer 检测不到声卡
- (Matlab实现)CNN卷积神经网络图片分类
- 初中高中睡前必看古诗名句
- 方差公式初三_九年级同步数学公式:方差公式(1)
- CMD命令查看WiFi密码
- 数字系统设计, 8个经典计数器电路方案合辑
- Windows和Linux入侵痕迹清理
- 域名CNAME记录不能同时适配根域名和www的解决方法
- 外卖订单量预测异常报警模型实践
- 【软考】【知识产权与法律法规】
热门文章
- Windows强行重置密码的影响
- Adaptec by PMC 8系列产品在Windows环境中的性能表现(二)
- 修改pptp默认端口号1723
- ABBYY Mac中如何实现PDF到Excel的快速转换
- js前端身份证完美验证
- spark提交命令中的jars设置方式
- DTL with变量
- 【Flutter从入门到入坑】Flutter 知识体系
- cv2.error: OpenCV(4.6.0) D:\a\opencv-python\opencv-python\opencv\modules\highgui\src\window.cpp:967:
- 1052. Linked List Sorting (25)