iOS之二维码扫描

    xiaoxiao2025-01-21  10

    最近复习,之前项目需求要实现一个二维码扫描的课程签到的功能。这里简单总结一下。

    根据文件的名字,相信也能猜出这俩文件实现的功能。

    首先说一下二维码扫描View的实现

    #import <UIKit/UIKit.h> @interface CaptureRectView : UIView - (void)setupView; @end

    这个接口里的方法被第一个文件调用的。

    - (void)setupView { [UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{ [self setNeedsDisplay]; } completion:^(BOOL finished) { }]; }

    该方法的具体实现。就是设置动画一些参数。在.m文件中还有私有方法,主要实现绘图和条形码的动画效果。

    //初始化设置view - (id)initWithFrame:(CGRect)frame{} //设置条形码的动画效果 - (void)lineScanning { CGRect animationRect = self.scanningImageView.frame;//条状 animationRect.origin.y += CGRectGetWidth(self.bounds) - CGRectGetMinX(animationRect) * 2 - CGRectGetHeight(animationRect); [UIView beginAnimations:nil context:nil]; [UIView setAnimationDelay:0];//延迟 [UIView setAnimationDuration:1.2]; [UIView setAnimationCurve:UIViewAnimationCurveLinear];//路径 速度线性 [UIView setAnimationRepeatCount:HUGE_VALF];//无限 [UIView setAnimationRepeatAutoreverses:NO];//自动反向执行 self.scanningImageView.hidden = NO; self.scanningImageView.frame = animationRect;//最终位置 加一段高度 [UIView commitAnimations]; } //对二维码的区域边界以及四个角画图 - (void)drawRect:(CGRect)rect { CGContextRef context = UIGraphicsGetCurrentContext(); CGContextSetFillColorWithColor(context, self.backgroundColor.CGColor); CGContextFillRect(context, rect); CGRect clearRect; CGFloat paddingX; CGContextClearRect(context, clearRect); CGContextSaveGState(context); UIImage *topLeftImage = [UIImage imageNamed:@"capture_1"]; UIImage *topRightImage = [UIImage imageNamed:@"capture_2"]; UIImage *bottomLeftImage = [UIImage imageNamed:@"capture_3"]; UIImage *bottomRightImage = [UIImage imageNamed:@"capture_4"]; CGFloat padding = 0.5; CGFloat originPadding = 0; CGContextMoveToPoint(context, CGRectGetMinX(clearRect) + originPadding, CGRectGetMinY(clearRect) + originPadding); CGContextAddLineToPoint(context, CGRectGetMaxX(clearRect) - originPadding, CGRectGetMinY(clearRect) + originPadding); CGContextAddLineToPoint(context, CGRectGetMaxX(clearRect) - originPadding, CGRectGetMaxY(clearRect) - originPadding); CGContextAddLineToPoint(context, CGRectGetMinX(clearRect) + originPadding, CGRectGetMaxY(clearRect) - originPadding); CGContextAddLineToPoint(context, CGRectGetMinX(clearRect) + originPadding, CGRectGetMinY(clearRect) + originPadding); CGContextSetLineWidth(context, padding); CGContextSetStrokeColorWithColor(context, [UIColor whiteColor].CGColor); CGContextStrokePath(context); [topLeftImage drawInRect:CGRectMake(clearRect.origin.x - 2.0, clearRect.origin.y - 2.0, topLeftImage.size.width, topLeftImage.size.height)]; } //lazy load条形码的设置以及文本的设置 -(UIImageView *)scanningImageView {} -(UILabel*)QRCodeTipLabel{}

    这里主要就是对UIView动画的学习以及绘图用的CGContex,是由Core Graphics来进行绘图

    Core Graphics

    既然说到绘图,最近也在复习,那就谈谈这个iOS绘图的方法。在iOS开发,用于绘图的主要有Core Graphics和UIkit。

    由上图不难看出,Core Graphics比UIkit要靠底层,UIkit是依赖Core Graphics的,UIkit继承UIResponse能够提供跟用户交互的功能,而Core Graphics是基础,只有在试图的基础上才能进行其他的功能。

    Core Graphics和UIKit在实际使用中也存在以下这些差异:

    1)Core Graphics其实是一套基于C的API框架,使用了Quartz作为绘图引擎。这也就意味着Core Graphics不是面向对象的。 2)Core Graphics需要一个图形上下文(Context)。所谓的图形上下文(Context),说白了就是一张画布。这一点非常容易理解,Core Graphics提供了一系列绘图API,自然需要指定在哪里画图。因此很多API都需要一个上下文(Context)参数。 3)Core Graphics的图形上下文(Context)是堆栈式的。只能在栈顶的上下文(画布)上画图。

    Core Graphics的基本使用

    为了使用CoreGraphics来绘图,最简单的方法就是自定义一个类继承自UIView,并重写子类的drawRect方法。在这个方法中绘制图形。 Core Graphics必须一个画布,才能把东西画在这个画布上。在drawRect方法方法中,我们可以直接获取当前栈顶的上下文(Context)。

    正如上面我在项目中写的一样。

    CGContextRef context = UIGraphicsGetCurrentContext();

    创建画布,然后就是可以根据项目的需求画各种各样的图形了,例如三角形啊,正方形啊等等,我项目中画的就是个矩形,我们扫描二维码时用的那个显示区域。

    CGContextMoveToPoint(context, CGRectGetMinX(clearRect) + originPadding, CGRectGetMinY(clearRect) + originPadding);

    这个就是画边界的线条的,我们还可以用自定义的图片,来增加美观。例如:

    UIImage *topLeftImage = [UIImage imageNamed:@"capture_1"]; [topLeftImage drawInRect:CGRectMake(clearRect.origin.x - 2.0, clearRect.origin.y - 2.0, topLeftImage.size.width, topLeftImage.size.height)];

    在左上角加上我们自定义的图片。

    Core Graphics绘图的步骤:

    获取上下文(画布) 创建路径(自定义或者调用系统的API)并添加到上下文中。 进行绘图内容的设置(画笔颜色、粗细、填充区域颜色、阴影、连接点形状等) 开始绘图(CGContextDrawPath) 释放路径(CGPathRelease)

    Core Graphics还有增加阴影,添加模糊效果,等很强大的功能。

    UIKit

    UIKit 像UIImage、NSString(绘制文本)、UIBezierPath(绘制形状)、UIColor都知道如何绘制自己。这些类提供了功能有限但使用方便的方法来让我们完成绘图任务。一般情况下,UIKit就是我们所需要的。

    使用UIKit,你只能在当前上下文中绘图,所以如果你当前处于UIGraphicsBeginImageContextWithOptions函数或drawRect:方法中,你就可以直接使用UIKit提供的方法进行绘图。如果你持有一个context:参数,那么使用UIKit提供的方法之前,必须将该上下文参数转化为当前上下文。幸运的是,调用UIGraphicsPushContext 函数可以方便的将context:参数转化为当前上下文,记住最后别忘了调用UIGraphicsPopContext函数恢复上下文环境。

    对UIView设置圆角

    - (void)drawRect:(CGRect)rect { CGContextRef ctx = UIGraphicsGetCurrentContext(); CGContextSetStrokeColorWithColor(ctx, [UIColor blackColor].CGColor); CGContextSetLineWidth(ctx, 3); UIBezierPath *path; path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(100, 100, 100, 100)  byRoundingCorners:(UIRectCornerTopLeft |UIRectCornerTopRight)         cornerRadii:CGSizeMake(10, 10)]; [path stroke]; }

    CoreAnimation

    在扫描二维码的时候,必须要让条形码移动,不然给用户体验超级不好,所以要用到该框架的一些方法,来对条形码的参数进行设置达到我们想要的效果。

    Core Animation是直接作用在CALayer上的(并非UIView上)非常强大的跨Mac OS X和iOS平台的动画处理API,Core Animation的动画执行过程都是在后台操作的,不会阻塞主线程。

    这个图很好的帮我们理解CoreAnimation底层是怎样实现的。

    1)首先得有CALayer(因为CoreAnimation是作用在CALayer上的)

    2)初始化一个CAAnimation对象,并设置一些动画相关属性

    3)通过调用CALayer的addAnimation:forKey:方法,增加CAAnimation对象到CALayer中,这样就能开始执行动画了

    4)通过调用CALayer的removeAnimationForKey:方法可以停止CALayer中的动画

    使用核心动画,你只需要设置一些参数比如起点和终点,剩下的帧核心动画为你自动完成。

    核心动画类有以下分类:

    提供显示内容的图层类。 动画和计时类。 布局和约束类。 事务类,在原子更新的时候组合图层类。

    1)图层类:

    核心动画的核心基础,它提供了一套抽象的概念(假如你使用过NSView或者UIView的话,你一定会对它很熟悉)。CALayer是整个图层类的基础,它是所有核心动画图层类的父类。

    虽然核心动画的图层和 Cocoa的视图在很大程度上没有一定的相似性,但是他们两者最大的区别是,图层不会直接渲染到屏幕上。

    在模型-视图-控制器(model-view-controller)概念里面NSView和UIView是典型的视图部分,但是在核心动画里面图层是模型部分。图层封装了几何、时间、可视化属性,同时它提供了图层现实的内容,但是实际显示的过程则不是由它来完成。

    三个重要的子类

    (1)CAScrollLayer:它是CALayer的一个子类,用来显示layer的某一部分,一个CAScrollLayer对象的滚动区域是由其子层的布局来定义的。CAScrollLayer没有提供键盘或者鼠标事件,也没有提供明显的滚动条。

    (2)CATextLayer:它是一个很方便就可以从string和attributed string创建layer的content的类。

    (3)CATiledLayer:它允许在增量阶段显示大和复杂的图像。

    2)动画类:

    CAPropertyAnimation :是一个抽象的子类,它支持动画的显示图层的关键路 径中指定的属性一般不直接使用,而是使用它的子类,CABasicAnimation,CAKeyframeAnimation. 在它的子类里修改属性来运行动画。

    CABasicAnimation: 简单的为图层的属性提供修改。 很多图层的属性修改默认会执行这个动画类。比如大小,透明度,颜色等属性 支持关键帧动画,你可以指定的图层属性的关键路径动画,包括动画的每个阶段的价值,以及关键帧时间和计时功能的一系列值。在 动画运行是,每个值被特定的插入值替代。核心动画 和 Cocoa Animation 同时使用这些动画类。

    3)布局管理器类:

    Application Kit 的视图类相对于 superlayer 提供了经典的“struts and springs”定位 模型。图层类兼容这个模型,同时 Mac OS X 上面的核心动画提供了一套更加灵活 的布局管理机制,它允许开发者自己修改布局管理器。核心动画的 CAConstraint 类 是一个布局管理器,它可以指定子图层类限制于你指定的约束集合。每个约束 (CAConstraint 类的实例封装)描述层的几何属性(左,右,顶部或底部的边缘或水 平或垂直中心)的关系,关系到其同级之一的几何属性层或 superlayer。通用的布局管理器和约束性布局管理器将会在“布局核心动画的图层”部分讨论。

    4) 事务管理类 :

    图层的动画属性的每一个修改必然是事务的一个部分。CATransaction 是核心动画里面负责协调多个动画原子更新显示操作。事务支持嵌套使用。

    [UIView beginAnimations:nil context:nil]; [UIView setAnimationDelay:0];//延迟 [UIView setAnimationDuration:1.2]; [UIView setAnimationCurve:UIViewAnimationCurveLinear];//路径 速度线性 [UIView setAnimationRepeatCount:HUGE_VALF];//无限 [UIView setAnimationRepeatAutoreverses:NO];//自动反向执行 self.scanningImageView.hidden = NO;

    具体代码可参考我开始项目中的具体例子。

    AVFoundation

    AVCaptureSession 管理输入(AVCaptureInput)和输出(AVCaptureOutput)流,包含开启和停止会话方法。

    AVCaptureDeviceInput 是AVCaptureInput的子类,可以作为输入捕获会话,用AVCaptureDevice实例初始化。

    AVCaptureDevice 代表了物理捕获设备如:摄像机。用于配置等底层硬件设置相机的自动对焦模式。

    AVCaptureMetadataOutput 是AVCaptureOutput的子类,处理输出捕获会话。捕获的对象传递给一个委托实现AVCaptureMetadataOutputObjectsDelegate协议。协议方法在指定的派发队列(dispatch queue)上执行。

    AVCaptureVideoPreviewLayerCALayer的一个子类,显示捕获到的相机输出流。

    1) 需要导入:AVFoundation Framework 包含头文件:

    #import <AVFoundation/AVFoundation.h>

    设置 AVCaptureSession 和 AVCaptureVideoPreviewLayer 成员

    2)创建会话,读取输入流

    //创建会话 AVCaptureSession *asession = [[AVCaptureSession alloc] init]; if ([asession canSetSessionPreset:AVCaptureSessionPresetHigh]) { [asession setSessionPreset:AVCaptureSessionPresetHigh];//采集质量 } else { [asession setSessionPreset:AVCaptureSessionPresetLow];//采集质量 } //获取AVCaptureDevice实例 _captureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; NSError *error = nil; if ([_captureDevice lockForConfiguration:&error]) { if ([_captureDevice isFocusModeSupported:AVCaptureFocusModeAutoFocus]) { [_captureDevice setFocusMode:AVCaptureFocusModeAutoFocus]; } [_captureDevice unlockForConfiguration]; } //初始化输入流 AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:_captureDevice error:&error]; if (error) { return; } if (!input) { return; } //初始化输出流 AVCaptureMetadataOutput *output = [[AVCaptureMetadataOutput alloc] init]; // 使用主线程队列,相应比较同步,使用其他队列,相应不同步,容易让用户产生不好的体验 [output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()]; //添加输入流,添加输出流 if ([asession canAddInput:input]) { [asession addInput:input]; } if ([asession canAddOutput:output]) { [asession addOutput:output]; } //设置元数据 [output setMetadataObjectTypes:[NSArray arrayWithObjects:AVMetadataObjectTypeQRCode, nil]]; //创建输出对象 AVCaptureVideoPreviewLayer *preview = [AVCaptureVideoPreviewLayer layerWithSession:asession]; [preview setVideoGravity:AVLayerVideoGravityResizeAspectFill]; [preview setFrame:self.view.bounds]; [self.view.layer insertSublayer:preview atIndex:0]; self.previewLayer = preview; //开始会话 dispatch_async(dispatch_get_global_queue(0, 0),^{ [asession startRunning]; }); self.session = asession;

    3)停止读取

    [self.view addSubview:self.nextStepView]; self.captureRectView = nil; [self.previewLayer removeFromSuperlayer]; //异步关闭,不影响主线程 dispatch_async(dispatch_get_global_queue(0, 0),^{ [self.session stopRunning]; self.session = nil; _captureDevice = nil;

    4)获取捕获数据处理结果 输出代理方法

    - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection { // 3. 设置界面显示扫描结果 if (metadataObjects.count > 0) { AVMetadataMachineReadableCodeObject *obj = metadataObjects[0]; if ([[obj type] isEqualToString:AVMetaObjectTypeQrCode]) { if ([obj.stringValue hasPrefix:@"http"] || [obj.stringValue rangeOfString:@"."].location != NSNotFound){ //配置url向服务器发送签到请求 } else { elf.nextStepLabel.text = [NSString stringWithFormat:@"二维码识别结果:\r\n%@",obj.stringValue]; } else } //不是二维码,请重新扫描 }

    当然要真正完成项目的功能还有很多细节,比如怎么阻止频繁扫描,怎么判断是否已经签到成功。因为项目原因,大家感兴趣可以自己写个小Demo试试。

    转载请注明原文地址: https://ju.6miu.com/read-1295693.html
    最新回复(0)