iOS扫码一图多码原生处理AVCaptureSession

news2024/12/24 9:09:30

文章目录

  • 前言
  • 正文
    • 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 mainThread
    
    WeakSelf
    dispatch_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];//多个二维码添加选择btn
    tempButton.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) {
              //  结果干点啥~~~~😊😊😊😊😊😊😊😊😊
            } else {
                NSLog(@"识别结果内容:为空");
                [weakSelf stopRunning];
            }
        } else {
            NSLog(@"没有网络");
            [weakSelf stopRunning];
        }
    }];
}

总结

思路看大厂,方法网上学,细节自己写,记录一手。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/83537.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

VSCode远程连接免密登录

配置了VSCode远程连接服务器&#xff0c;但每次打开project都需要重新输入密码&#xff0c;比较麻烦&#xff0c;所以下面就介绍一下如何免密码登入 在上一篇blog里面配置好VSCode远程连接服务器之后按照如下操作。 步骤如下&#xff1a; 在windows端&#xff1a; 1、winR打…

softmax原理性质解析并python实现

Softmax原理 Softmax函数用于将分类结果归一化&#xff0c;形成一个概率分布。作用类似于二分类中的Sigmoid函数。 对于一个k维向量z&#xff0c;我们想把这个结果转换为一个k个类别的概率分布p(z)。softmax可以用于实现上述结果&#xff0c;具体计算公式为&#xff1a; 对于k…

【数集项目之 MCDF】(一) 控制寄存器 control_register

写在前面 本项目为MCDF数据整形器设计&#xff0c;所有的参考代码见我的github https://github.com/SuperiorLQF/verilog_ALL/tree/master/MCDF 其中设计的参考文档见github文件中的MCDF修订版.docx文件。选择的工具链是Vscode & iverilog & gtkwave&#xff0c;相关工…

前端框架 Nuxt3 Vue3 SSR 总结

目录 一、Nuxt3安装 二、路由 1、普通路由 2、动态路由 3、获取路由参数 4、路由跳转标签 5、路由跳转api 三、静态资源 四、常用标签 1、title标签、useHead的API 五、公共模板布局 1、默认布局 2、自定义公共模板 3、动态自定义布局 六、插件 七、中间件 …

【QString 函数学习篇】

【QString 函数学习篇】【1】UI设计布局【2】QChar | setAlignment |【3】sprintf | asprintf | setNum | number |【4】toInt | toUpper [十进制->十六进制 | 十进制->二进制]【5】clear | append【6】二进制->十六进制 | 二进制->十进制【7】prepend【8】strimme…

[附源码]Python计算机毕业设计SSM基于的餐厅管理系统(程序+LW)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

(十二)Vue之列表渲染

文章目录基本列表遍历数组遍历对象遍历字符串遍历指定次数key的原理虚拟DOM中key的作用用index作为key可能会引发的问题如何选择keyVue学习目录 上一篇&#xff1a;&#xff08;十一&#xff09;Vue之条件渲染 基本列表 在vue里基本的列表渲染可以使用v-for指令 v-for指令: …

Reactor手册

Flux Flux 是一个发出0-N个元素组成的异步序列的Publisher,可以被onComplete信号或者onError信号所终止。 Flux.just("Hello", "World").subscribe(System.out::println);// fromArray()&#xff0c;fromIterable()&#xff0c;fromStream()Flux.fromArra…

K8s 核心组件介绍

目录前言一、控制平面组件1.1 kube-apiserver1.2 etcd1.3 kube-scheduler1.4 kube-controller-manager1.5 cloud-controller-manager二、Node 组件2.1 kubelet2.2 kube-proxy2.3 Container Runtime前言 一个完整的 K8s 集群由一组节点&#xff08;node&#xff09;服务器组成&…

组队-蓝桥杯

题目描述 本题为填空题&#xff0c;只需要算出结果后&#xff0c;在代码中使用输出语句将所填结果输出即可。 作为篮球队教练&#xff0c;你需要从以下名单中选出 11 号位至 55 号位各一名球员&#xff0c;组成球队的首发阵容。 每位球员担任 11 号位至 55 号位时的评分如下…

关于人脸检测和人脸关键点检测的详解(涉及Opencv 和Dlibd)

关于人脸识别&#xff0c;大家入门opencv&#xff0c;最常见的是用opencv级联分类器器里面的函数进行人脸的识别&#xff08;当然里面包含很多各种物体的分类器&#xff0c;大家可以一一测试&#xff09;&#xff0c;今天我们来练一下关于人脸识别的级联器。 1&#xff0c;ope…

数据仓库(DW)、数据湖、数据中台的关系

一句话说明&#xff1a;数据中台是一套体系&#xff0c;既不是工具又不是存储&#xff0c;它可以包含数据湖和数据仓库。 数据仓库 数据仓库是一个面向主题的、集成的、随时间变化但信息本身相对稳定的数据集合&#xff0c;用于支持管理决策过程。其本质就是完成从面向业务过程…

[附源码]Python计算机毕业设计Django-菜篮子系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

一文学会jenkins pipline自动化构建

01 Pipeline流水线基本语法 首先创建 在jenkins上创建一个pipeline的流水线任务 新建ITEM–>选择流水线 基本pipeline脚本结构 pipeline {//agent 表示要执行的节点&#xff0c;any表示任意节点 agent any //stages表示任务执行时的所有步骤集合 stages { /…

企业销售CRM的主要优势是什么?

民营企业商品销售CRM的主要就竞争优势是什么? 新一代研究说明&#xff0c;由于差劲的顾客新体验&#xff0c;或内公司每月经济损失750亿元。为了更快地介绍您的顾客&#xff0c;您须要两个智能化的顾客管理工作系统。因而&#xff0c;您能提供更多直接影响您的商品销售的高质…

Python调用C++

1 背景 python被称为胶水语言&#xff0c;其优势是能够粘结各种不同的语言。同时&#xff0c;python有着更大的“亲民性”&#xff0c;很容易进行开发。但是&#xff0c;python最大的问题就是计算速度不够。通常可以用CUDA或者C对一个python程序进行加速&#xff0c;加速策略如…

【LeetCode】单词搜索 II [H](前缀树)

212. 单词搜索 II - 力扣&#xff08;LeetCode&#xff09; 一、题目 给定一个 m x n 二维字符网格 board 和一个单词&#xff08;字符串&#xff09;列表 words&#xff0c; 返回所有二维网格上的单词 。 单词必须按照字母顺序&#xff0c;通过 相邻的单元格 内的字母构成&am…

QT系列第2节 QT中元对象系统

QT是在标准C上进行了扩展&#xff0c;所以就有自己的特性&#xff0c;其中元对象系统就是其一。元对象系统有点类似于java和go语言中的反射&#xff0c;让我们在编程时解决问题多了些方法和思路&#xff0c;关于元对象可以简单总结出以下内容项。 目录 一.元对象要点总结 二…

Linux转发性能评估与优化之——转发瓶颈分析与解决方案

线速问题 很多人对这个线速概念存在误解。认为所谓线速能力就是路由器/交换机就像一根网线一样。而这&#xff0c;是不可能的。应该考虑到的一个概念就是延迟。数据包进入路由器或者交换机&#xff0c;存在一个核心延迟操作&#xff0c;这就是选路&#xff0c;对于路由器而言&…

软件工程复习简略

软件工程复习简略1.什么是软件生存周期&#xff1f;通常可划分为哪些阶段&#xff1f;2.简述需求分析要经过哪些步骤&#xff0c;每个步骤的作用。3.详细设计有哪些常用工具&#xff1f;&#xff08;注意Pad图的画法&#xff09;4.软件测试的目的和原则是什么&#xff1f;5.测试…