媒体捕捉-iOS自定义二维码扫描功能

news2024/9/27 5:48:47

引言

随着iOS 7引入AV Foundation框架,二维码扫描功能已经成为iOS应用程序中不可或缺的一部分。现今,几乎每个应用都充分利用这一功能,为用户提供了诸如扫码登录、扫码填充等丰富多彩的便捷体验。这项技术不仅丰富了应用功能,也为开发人员的调试工作带来了极大便利。在本博客中,我们将深入探讨如何实现自定义二维码扫描,为您打开更广阔的应用开发可能性。

主要类-AVCaptureMetadataOutput

在二维码扫描中,我们仍然以AVCaptureSession为核心,配置输入(AVCaptureDeviceInput)和输出(AVCaptureOutput)。特别是在输出方面,我们使用AVCaptureMetadataOutput来专门处理摄像头捕获到的二维码、条形码等元数据。

通过设置metadataObjectTypes属性,我们可以灵活地指定要捕获的元数据类型,如二维码、QR码、条形码等。这样的设置允许我们精确控制扫描的类型,提高识别效率。

通过实现AVCaptureMetadataOutputObjectsDelegate代理,我们能够在识别到指定类型的元数据时触发相应的代理方法,从而处理这些元数据。这为开发者提供了处理扫描结果的机会,使得在应用中集成二维码扫描功能变得更为灵活和可定制。

//MARK:检测到指定类型元数据
- (void)captureOutput:(AVCaptureOutput *)output didOutputMetadataObjects:(NSArray<__kindof AVMetadataObject *> *)metadataObjects fromConnection:(AVCaptureConnection *)connection{

}

功能实现

为了使描述更清晰,我们将二维码识别和元数据处理分别放置到两个不同的类里面来处理。

PHCameraController:主要负责启动会话,开启进行二维码识别。

PHPreviewView:我们在这个view里面来处理和呈现元数据。

PHCameraController

和其它媒体捕捉的功能几乎一样,配置会话,启动会话。不同的是会话的输出实现,另外我们还在里面定义了一个协议,当有二维码被识别到时会调用这个协议中的方法,将元数据传递给PHPreviewView。

接口:

下面看一下PHCameraController中接口的定义。

#import <AVFoundation/AVFoundation.h>
NS_ASSUME_NONNULL_BEGIN
@protocol PHCodeDetectionDelegate <NSObject>
- (void)didDetectCodes:(NSArray *)codes;
@end
@interface PHCameraController : NSObject
@property(nonatomic,weak)id <PHCodeDetectionDelegate>  codeDetectionDelegate;
@property(nonatomic,strong,readonly)AVCaptureSession * captureSession;

///设置会话
- (BOOL)setupSession:(NSError **)error;
///开始会话
- (void)startSession;
///停止会话
- (void)stopSession;

@end
NS_ASSUME_NONNULL_END

外漏了一个只读的AVCaptureSession和三个核心的方法。以及一个codeDetectionDelegate。

实现:

接下来看一下PHCameraController的实现,会相对复杂一点,但我们只需要将关注点放到配置会话输出以及AVCaptureMetadataOutputObjectsDelegate的代理方法上面。

设置会话:
#import "PHCameraController.h"
#import <UIKit/UIKit.h>
@interface PHCameraController ()<AVCaptureMetadataOutputObjectsDelegate>
@property(nonatomic,strong)AVCaptureMetadataOutput * metadataOutput;
@end
@implementation PHCameraController

- (NSString *)sessionPreset{
    return AVCaptureSessionPreset640x480;
}

//MARK:设置会话
- (BOOL)setupSession:(NSError *__autoreleasing  _Nullable *)error{
    self.captureSession = [[AVCaptureSession alloc] init];
    self.captureSession.sessionPreset = self.sessionPreset;
    if (![self setupSessionInputs:error]) {
        return NO;
    }
    if (![self setupSessionOutputs:error]) {
        return NO;
    }
    return YES;
}
配置会话输入:

//MARK:配置会话输入
- (BOOL)setupSessionInputs:(NSError *__autoreleasing  _Nullable *)error{
    BOOL success = [super setupSessionInputs:error];
    if (success) {
        if (self.activeCamera.autoFocusRangeRestrictionSupported) {
            if ([self.activeCamera lockForConfiguration:error]) {
                self.activeCamera.autoFocusRangeRestriction = AVCaptureAutoFocusRangeRestrictionNear;
                [self.activeCamera unlockForConfiguration];
            }
        }
    }
    return success;
}

配置会话输出:
//MARK:配置会话输出
- (BOOL)setupSessionOutputs:(NSError *__autoreleasing  _Nullable *)error{
    self.metadataOutput = [[AVCaptureMetadataOutput alloc] init];
    if ([self.captureSession canAddOutput:self.metadataOutput]) {
        [self.captureSession addOutput:self.metadataOutput];
        NSArray * metadataObjectTypes = @[AVMetadataObjectTypeQRCode,AVMetadataObjectTypeAztecCode,AVMetadataObjectTypeUPCECode];
        self.metadataOutput.metadataObjectTypes = metadataObjectTypes;
        dispatch_queue_t mainQueue = dispatch_get_main_queue();
        [self.metadataOutput setMetadataObjectsDelegate:self queue:mainQueue];
        return YES;
    }else{
        return NO;
    }
}
识别到指定类型元数据的回调:
//MARK:检测到指定兴趣点的代理
- (void)captureOutput:(AVCaptureOutput *)output didOutputMetadataObjects:(NSArray<__kindof AVMetadataObject *> *)metadataObjects fromConnection:(AVCaptureConnection *)connection{
    [self.codeDetectionDelegate didDetectCodes:metadataObjects];
}

PHPreviewView

主要负责显示预览画面,及显示元数据内容。

接口:
#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface PHPreviewView : UIView

@property(nonatomic,strong)AVCaptureSession * session;


@end

NS_ASSUME_NONNULL_END
实现:
initWithFrame:方法:
@interface PHPreviewView ()<PHCodeDetectionDelegate>


@end

@implementation PHPreviewView

- (id)initWithFrame:(CGRect)frame{
    if ([super initWithFrame:frame]) {
        [self setupView];
    }
    return self;
}


- (void)setupView{
    self.codeLayers = @{}.mutableCopy;
    self.previewLayer.videoGravity = AVLayerVideoGravityResizeAspect;
}

遵守了一个PHCodeDetectionDelegate协议。

设置预览layer的画面填充方式。

重写方法:

同样我们通过重写layerClass类方法来返回一个AVCaptureVideoPreviewLayer类。

通过重写session的setter来为AVCaptureVideoPreviewLayer设置session。

通过重写session的getter来返回当前session。

+ (Class)layerClass{
    return [AVCaptureVideoPreviewLayer class];
}

- (void)setSession:(AVCaptureSession *)session{
    [(AVCaptureVideoPreviewLayer*)self.layer setSession:session];
}

- (AVCaptureSession *)session{
    return [(AVCaptureVideoPreviewLayer*)self.layer session];
}
定义previewLayer的getter:
//MARK:当前显示画面layer
- (AVCaptureVideoPreviewLayer *)previewLayer{
    return (AVCaptureVideoPreviewLayer *)self.layer;
}
实现PHCodeDetectionDelegate代理:

//MARK:代理 检测到条码
- (void)didDetectCodes:(NSArray *)codes{
    if (codes.count <= 0) {
        return;
        
    }
    NSArray * transformedCodes = [self transformedCodesFromCodes:codes];
    for (AVMetadataMachineReadableCodeObject * code in transformedCodes) {
        NSString * stringValue = code.stringValue;
        if (self.codeArrowLayers.count == 0) {
            self.codeArrowLayers = [self makeCornersLayers];
        }
        for (int i = 0; i < self.codeArrowLayers.count; i ++) {
            if (i >= code.corners.count) {
                break;
            }
            CAShapeLayer * layer = self.codeArrowLayers[i];
            [self.previewLayer addSublayer:layer];
            layer.path = [self bezierPathWithCodes:code.corners index:i].CGPath;
        }
        NSLog(@"String :%@",stringValue);
    }
    [self.delegate didDetectCodes:codes];
}

关于这个方法我们需要分成几部分来理解。

首先调用了一个transformedCodesFromCodes:方法,只是我们自己定义的方法,在里面我们只是调用了AVCaptureVideoPreviewLayer提供的坐标转发方法将设备坐标空间元数据对象转换为视图坐标空间对象,实现如下:

//MARK:坐标转换
- (NSArray *)transformedCodesFromCodes:(NSArray *)codes{
    NSMutableArray * transformedCodes = [NSMutableArray array];
    for (AVMetadataObject * code in codes) {
        AVMetadataObject * transformedCode = [self.previewLayer transformedMetadataObjectForMetadataObject:code];
        [transformedCodes addObject:transformedCode];
    }
    return transformedCodes;
}

读取到的AVMetadataMachineReadableCodeObject对象里面会有一个bounds和corners两个数组,bounds属性提供了识别码的按坐标轴对其的矩形边界,corners属性提供角点字典表示的NSArray。后一个属性更实用,因为使用它可以让我们构建一个与条码的角点坐标紧密对其的Bezier路径。当然还有一个更重要的属性是stringValue也就是我们从二维码中读取到的内容。

示例中我们使用它的corners属性绘制出了二维码的顶点轮廓,实现如下:

创建轮廓layer:

//MARK: makeCornersLayers
- (NSArray *)makeCornersLayers{
    CAShapeLayer * leftTop = [[CAShapeLayer alloc] init];
    CAShapeLayer * leftBottom = [[CAShapeLayer alloc] init];
    CAShapeLayer * rightBottom = [[CAShapeLayer alloc] init];
    CAShapeLayer * rightTop = [[CAShapeLayer alloc] init];
    NSArray * array = @[leftTop,leftBottom,rightBottom,rightTop];
    for (CAShapeLayer * layer in array) {
        layer.strokeColor = [UIColor redColor].CGColor;
        layer.fillColor = [UIColor clearColor].CGColor;
        layer.lineWidth = 2.0;
    }
    return  array;
}

绘制轮廓path:

- (UIBezierPath *)bezierPathWithCodes:(NSArray *)codes index:(NSInteger)index{
    CGPoint point;
    CGPointMakeWithDictionaryRepresentation((CFDictionaryRef)codes[index], &point);
    CGPoint prePoint = CGPointZero;
    CGPoint postPoint = CGPointZero;
    switch (index) {
        case 0:
        {
            prePoint = CGPointMake(point.x, point.y + 10);
            postPoint = CGPointMake(point.x + 10, point.y);
        }
            break;
        case 1:
        {
            prePoint = CGPointMake(point.x, point.y - 10);
            postPoint = CGPointMake(point.x + 10, point.y);
        }
            break;
        case 2:
        {
            prePoint = CGPointMake(point.x - 10, point.y);
            postPoint = CGPointMake(point.x, point.y - 10);
        }
            break;
        case 3:
        {
            prePoint = CGPointMake(point.x, point.y + 10);
            postPoint = CGPointMake(point.x - 10, point.y);
        }
            break;
    }
    UIBezierPath * path = [UIBezierPath bezierPath];
    [path moveToPoint:prePoint];
    [path addLineToPoint:point];
    [path addLineToPoint:postPoint];
    return path;
}

最后代理调用didDetectCodes:方法将结果传递到视图控制器,并停止会话。

视图控制器实现:

import "ViewController.h"
#import "PHPreviewView"
#import "PHCameraController.h"

@interface ViewController ()<PHPreviewViewDelegate>

///相机控制
@property(nonatomic,strong)PHCameraController * controller;
///预览view
@property(nonatomic,strong)PHPreviewView * previewView;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self setupView];
}

- (void)setupView{
    [self addPreviewView];
    [self configController];
}

//MARK: 添加预览视图
- (void)addPreviewView{
    self.previewView = [[PHPreviewView alloc] initWithFrame:self.view.bounds];
    [self.view addSubview:self.previewView];
    self.previewView.delegate = self;
}

- (void)didDetectCodes:(NSArray *)codes{
    [self.controller stopSession];
}


- (BOOL)prefersStatusBarHidden{
    return YES;
}


@end

结语

当我们深入研究了上述简单的二维码扫描示例后,我们不禁会发现这只是冰山一角。iOS提供了丰富的功能和灵活的接口,让我们能够进一步深挖二维码扫描的世界。举例来说,我们可以定义扫描范围,通过调整参数来适应各种应用场景,从而提高扫描的精准度和效率。

此外,iOS还支持各种花哨的识别效果,可以为用户提供更为生动和愉悦的扫描体验。通过巧妙运用动画、声音等元素,我们能够使二维码扫描不仅仅是一项实用的功能,更是一种与用户互动的方式。

在这个不断创新和演变的移动应用时代,探索二维码扫描功能的可能性就像打开了一扇通往无限可能性的大门。无论是为了提升用户体验,还是为了创造独特的应用功能,二维码扫描都为开发者提供了丰富的创作空间。让我们在不断探索的道路上,发现更多精彩的可能性吧。

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

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

相关文章

我与nano实验室交流群

感兴趣的同学、朋友可以加入群聊共同学习聊天哦。 主要是工训赛、电赛、光电、集成电路等等&#xff0c;会分享一些开源代码&#xff0c;博主自己做的项目&#xff0c;自己画的PCB等等&#xff0c;包含但不限于STM32、K210、V831、机器视觉&#xff0c;机械臂&#xff0c;ROS&a…

Packet Tracer - Configure AAA Authentication on Cisco Routers

Packet Tracer - 在思科路由器上配置 AAA 认证 地址表 目标 在R1上配置本地用户账户&#xff0c;并使用本地AAA进行控制台和vty线路的身份验证。从R1控制台和PC-A客户端验证本地AAA身份验证功能。配置基于服务器的AAA身份验证&#xff0c;采用TACACS协议。从PC-B客户端验证基…

我的隐私计算学习——联邦学习(2)

笔记内容来自多本书籍、学术资料、白皮书及ChatGPT等工具&#xff0c;经由自己阅读后整理而成 &#xff08;三&#xff09;联邦学习的算子 ------------------------ 算子是什么&#xff1f;--------------------------- ​ 从广义上讲&#xff0c;对任何函数进行某一项操作都可…

【Verilog】基于Verilog的DDR控制器的简单实现(一)——初始化

在FPGA中&#xff0c;大规模数据的存储常常会用到DDR。为了方便用户使用&#xff0c;Xilinx提供了DDR MIG IP核&#xff0c;用户能够通过AXI接口进行DDR的读写访问&#xff0c;然而MIG内部自动实现了许多环节&#xff0c;不利于用户深入理解DDR的底层逻辑。 本文以美光(Micro…

Linux驱动学习—中断

1、中断基础概念 1.1 什么是中断 CPU在正常运行期间&#xff0c;由外部或者内部引起的时间&#xff0c;让CPU停下当前正在运行的程序&#xff0c;转而去执行触发他的中断所对应的程序&#xff0c;这就是中断。 响应中断的过程&#xff1a; <1>中断请求 <2>中断…

给您的应用添加弹窗

概述 在我们日常使用应用的时候&#xff0c;可能会进行一些敏感的操作&#xff0c;比如删除联系人&#xff0c;这时候我们给应用添加弹窗来提示用户是否需要执行该操作&#xff0c;如下图所示&#xff1a; 弹窗是一种模态窗口&#xff0c;通常用来展示用户当前需要的或用户必须…

cookie和session、请求转发和重定向

会话 分为有状态会话和无状态会话 在HTML中&#xff0c;"会话"一般指的是Web服务器与客户端&#xff08;通常是浏览器&#xff09;之间进行的一系列请求和响应。它是一种在网络上模拟人与人之间通信的方式&#xff0c;常见于Web应用程序中。 会话、Cookie和Sessio…

JavaScript 基础二part1.运算符:赋值、一元、比较、逻辑运算符

JavaScript 基础二 1.1 赋值运算符1.2 一元运算符自增运算符的用法&#xff1a;例题 1.3 比较运算符不同类型间的比较严格相等对 null 和 undefined 进行比较 1.4 逻辑运算符例题 1.5 运算符优先级 1.1 赋值运算符 赋值运算符&#xff1a;对变量进行赋值的运算符 已经学过的赋…

c++学习第八讲---类和对象---继承

继承&#xff1a; 使子类&#xff08;派生类&#xff09;拥有与父类&#xff08;基类&#xff09;相同的成员&#xff0c;以节约代码量。 1.继承的基本语法&#xff1a; class 子类名&#xff1a;继承方式 父类名{} &#xff1b; 例&#xff1a; class father { public:in…

李沐-《动手学深度学习》-- 01-预备知识

一、线性代数知识 1. 矩阵计算 a. 矩阵求导 ​ 当y和x分别为标量和向量时候&#xff0c;进行求导得到的矩阵形状&#xff0c;矩阵求导就是矩阵A中的每一个元素对矩阵B中的每一个元素求导 ​ 梯度指向的是值变化最大的方向 ​ 分子布局和分母布局&#xff1a; b. 常识 ax…

cube生成电机库,启用了RTOS,编译报错[0xc43ed8:5050106] in osSignalWait

cube生成电机库&#xff0c;启用了RTOS&#xff0c;编译报错[0xc43ed8:5050106&#xff0c;解决办法] in osSignalWait 1.现象 编译报错[0xc43ed8:5050106] in osSignalWait 导致链接失败 2.解决办法 将keil5的版本升级到5.18.00&#xff0c;我的版本也是5.14.00。

我的第一个前端项目,vue项目从零开始创建和运行

​入门前端&#xff0c;从基础做起&#xff0c;从零开始新建项目 背景&#xff1a;VUE脚手架项目是一个“单页面”应用&#xff0c;即整个项目中只有1个网页&#xff01; 在VUE脚手架项目中&#xff0c;主要是设计各个“视图组件”&#xff0c;它们都是整个网页中某个部分&…

Python如何生成个性二维码

Python-生成个性二维码 一、问题描述 通过调用MyQR模块来实现生成个人所需二维码。 安装&#xff1a; pip install myqr 二、代码实现 1.普通二维码 from MyQR import myqr # 普通二维码 myqr.run(wordshttp://www.csdn.net/mayi0312,save_nameqrcode.png ) 效果图&#…

学习录

概述 这几年在迷茫中看了不少资料&#xff0c;有觉得写得很棒的&#xff0c;也有写的很糟糕的。所以一直想写这块的总结来进行归纳&#xff0c;同时也希望能给其他处于迷茫中的朋友提供一份高质量的资料列表(也许一个读者也没有)&#xff0c;以下清单个人觉得值得反复看以及思…

利用ChatGLM3构建Prompt实现Text2SQL

之前使用ChatGLM3的自定义工具实现了查询MySQL数据库&#xff0c;但感觉功能还是比较受限。 https://blog.csdn.net/weixin_44455388/article/details/135270879?spm1001.2014.3001.5501 使用ChatGLM3实现Text2SQL 前言Text2SQL的构建第一阶段&#xff1a;SQL脚本构建&#xf…

听GPT 讲Rust源代码--compiler(32)

File: rust/compiler/rustc_middle/src/middle/exported_symbols.rs 在Rust的源代码中&#xff0c;rust/compiler/rustc_middle/src/middle/exported_symbols.rs文件的作用是实现编译器中处理导出符号的功能。 该文件中定义了一些结构体和枚举&#xff0c;用于描述导出符号的信…

MySQL 存储引擎和索引类型介绍

1. 引言 MySQL 是一个流行的关系型数据库管理系统&#xff0c;提供多种存储引擎以满足不同的业务需求。本文将介绍几种常见的 MySQL 存储引擎和索引类型比较&#xff0c;并给出相应的示例。 2. 存储引擎概述 2.1 InnoDB 存储引擎 InnoDB 是 MySQL 的默认存储引擎&#xff0…

向量数据库:usearch的简单使用+实现图片检索应用

usearch的简单使用 usearch是快速开源搜索和聚类引擎&#xff0c;用于C、C、Python、JavaScript、Rust、Java、Objective-C、Swift、C#、GoLang和Wolfram &#x1f50d;中的向量和&#x1f51c;字符串 // https://github.com/unum-cloud/usearch/blob/main/python/README.md …

解决ImportError: Failed to import test module: sys.__init__

解决ImportError: Failed to import test module: sys.init 背景 学习通过文件夹执行测试脚本时&#xff0c;出现了错误&#xff1a;ImportError: Failed to import test module: sys.__init__ 解决过程 根据报错信息&#xff1a;sys is not a package大胆猜测可能是文件名…

【MySQL】数据库之MMM高可用

目录 一、什么是MMM 二、关于MMM架构的说明 三、实操MMM的高可用 步骤一&#xff1a;完成主主复制、主从复制 步骤二&#xff1a;所有节点服务器都安装mysql-mmm,并完成mmm_common.conf文件的配置 步骤三&#xff1a;完成monitor节点服务器的配置文件修改mmm_mon.conf 步…