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

引言

随着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;@endNS_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.mzph.cn/news/604600.shtml

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

相关文章

pyfolio工具结合backtrader分析量化策略组合,附源码+问题分析

pyfolio可以分析backtrader的策略&#xff0c;并生成一系列好看的图表&#xff0c;但是由于pyfolio直接install的稳定版有缺陷&#xff0c;开发版也存在诸多问题&#xff0c;使用的依赖版本都偏低&#xff0c;试用了一下之后还是更推荐quantstats。 1、安装依赖 pip install …

车辆运动学方程推导和代码实现

文章目录 1. 运动学方程2. 模型实现 1. 运动学方程 自行车模型&#xff08;Bicycle Model&#xff09;是车辆数字化模型中最常见的一种运动学模型。其除了可以反映车辆的一些基础特性外&#xff0c;更重要的是简单易用。通常情况下我们会把车辆模型简化为二自由度的自行车模型…

游戏Lua调用01.lua的编译及测试

一、lua库下载与编译 进入lua官网 Lua: version history 找到lua5.1 选择lua5.1是因为大部分游戏使用的都是lua5.1的库&#xff0c;也可以选择高版本&#xff0c;影响不大 下载完了后使用vs建立一个静态库或者动态库的工程 这里以动态库为例子&#xff0c;静态库也是一样的…

flink table view datastream互转

case class outer(f1:String,f2:Inner) case class outerV1(f1:String,f2:Inner,f3:Int) case class Inner(f3:String,f4:Int) 测试代码 package com.yy.table.convertimport org.apache.flink.streaming.api.scala.StreamExecutionEnvironment import org.apache.flink.tabl…

w18认证崩溃之暴力破解DVWA

一、实验环境 攻击工具&#xff1a;burpsuite2021.12 靶场&#xff1a;DVWA二、实验目的 演示暴破DVWA的medium和high两个级别&#xff0c;low级别请查看w18认证崩溃之暴力破解4种攻击模式 三、实验步骤 1.设置靶场medium级别 2.开启谷歌代理插件&#xff0c;开启bp拦截&…

AI教我学编程之AI自刀

AI教我学编程系列学习第二课 — C#变量类型 上节回顾知识梳理C#基本变量类型 对话AI分歧产生本段总结 它说得对吗&#xff1f;我随即发问经典AI自刀他来了 总结 上节回顾 在上一节中我们发现&#xff0c;AI工具似乎还不能达到教学的水平&#xff0c;所以在本节中&#xff0c;…

数据在内存中的存储方式

前言&#xff1a; 期末临近&#xff0c;继续复习&#xff01; 今天要复习的内容是数据在内存中的存储&#xff0c;主要是整型与浮点两种&#xff0c;还有大小端的介绍。 提出问题 打印结果是255 -1 为什么&#xff1f; 首先我们要知道数据都是以二进制的形式存…

Spring Framework和SpringBoot的区别

目录 一、前言 二、什么是Spring 三、什么是Spring Framework 四、什么是SpringBoot 五、使用Spring Framework构建工程 六、使用SpringBoot构建工程 七、总结 一、前言 作为Java程序员&#xff0c;我们都听说过Spring&#xff0c;也都使用过Spring的相关产品&#xff0…

uni-app 前后端调用实例 基于Springboot 详情页实现

锋哥原创的uni-app视频教程&#xff1a; 2023版uniapp从入门到上天视频教程(Java后端无废话版)&#xff0c;火爆更新中..._哔哩哔哩_bilibili2023版uniapp从入门到上天视频教程(Java后端无废话版)&#xff0c;火爆更新中...共计23条视频&#xff0c;包括&#xff1a;第1讲 uni…

mysql进阶-重构表

目录 1. 原因 2. 如何重构表呢&#xff1f; 2.1 命令1&#xff1a; 2.2 命令2&#xff1a; 2.3 命令3&#xff1a; 1. 原因 正常的业务开发&#xff0c;为什么需要重构表呢&#xff1f; 原因1&#xff1a;某张表存在大量的新增和删除操作&#xff0c;导致表经历过大量的…

JavaScript异常处理实战

前言 之前在对公司的前端代码脚本错误进行排查&#xff0c;试图降低 JS Error 的错误量&#xff0c;结合自己之前的经验对这方面内容进行了实践并总结&#xff0c;下面就此谈谈我对前端代码异常监控的一些见解。 本文大致围绕下面几点展开讨论&#xff1a; JS 处理异常的方式…

解决 Postman 报错问题:一份综合指南

Postman 是一个流行的 API 测试工具&#xff0c;它可以帮助开发者和测试人员快速地创建和发送各种 HTTP 请求&#xff0c;并查看响应结果。但是&#xff0c;在使用 Postman 的过程中&#xff0c;有时候会遇到一些报错或异常情况&#xff0c;影响了正常的测试流程。本文将介绍一…

图像分割-Grabcut法

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 本文的C#版本请访问&#xff1a;图像分割-Grabcut法(C#)-CSDN博客 GrabCut是一种基于图像分割的技术&#xff0c;它可以用于将图像…

循环队列的队空队满情况

有题目&#xff1a; 循环队列放在一维数组A[0....M-1]中&#xff0c;end1指向队头元素&#xff0c;end2指向队尾元素的后一个位置。假设队列两端均可进行入队和出队操作&#xff0c;队列中最多能容纳M-1个元素。初始时为空。下列判断队空和队满的条件中&#xff0c;正确的是 …

vim/vi 模式切换和常用快捷键

vim/vi 切换模式&#xff1a; vim/vi 常用快捷键&#xff1a; 一般模式&#xff1a; gg&#xff1a;文件开头、G&#xff1a;文件结尾 shift^ &#xff1a;光标当前行首、shift^&#xff1a;光标当前行尾 yy&#xff1a;复制、p&#xff1a;粘贴、dd&#xff1a;删除当前行、…

CRM的request管理笔记

1 request类型 request有两种&#xff0c;device request和link request。 link request link req是对link进行精确控制。 link req是对每个link的请求&#xff0c;比如某一帧是否需要bubble recovery、某一帧是否需要长曝光等feature。device request 对一个设备进行每帧控制…

JDK 11:崭新特性解析

JDK 11&#xff1a;崭新特性解析 JDK 11&#xff1a;崭新特性解析1. HTTP Client&#xff08;标准化&#xff09;示例代码 2. 局部变量类型推断的扩展示例代码 3. 新的字符串方法示例代码 4. 动态类文件常量示例代码 5. Epsilon 垃圾收集器使用方式 结语 JDK 11&#xff1a;崭新…

MySQL数据库进阶-事务

事务 事务由单独单元的一个或多个SQL语句组成&#xff0c;在这 个单元中&#xff0c;每个MySQL语句是相互依赖的。而整个单独单 元作为一个不可分割的整体&#xff0c;如果单元中某条SQL语句一 旦执行失败或产生错误&#xff0c;整个单元将会回滚。所有受到影 响的数据将返回到…

现有网络模型的使用及修改(VGG16为例)

VGG16 修改默认路径 import os os.environ[TORCH_HOME] rD:\Pytorch\pythonProject\vgg16 # 下载位置太大了&#xff08;140多G&#xff09;不提供直接下载 train_set torchvision.datasets.ImageNet(root./data_image_net, splittrain, downloadTrue, transformtorchvis…

Informer:用于长序列时间序列预测的高效Transformer模型

最近在研究时间序列分析的的过程看&#xff0c;看到一篇精彩的文章&#xff0c;名为&#xff1a;《Informer: Beyond Efficient Transformer for Long Sequence Time-Series Forecasting》&#xff0c;特此撰写一篇博客。 文章主要研究了一种用于长序列时间序列预测的高效Trans…