博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
iOS之二维码扫描
阅读量:4109 次
发布时间:2019-05-25

本文共 10421 字,大约阅读时间需要 34 分钟。

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

这里写图片描述

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

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

#import 
@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 

设置 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试试。

你可能感兴趣的文章
wireshark报The capture session could not be initiated 错误
查看>>
MDK中加载指定文件的技巧
查看>>
stm32 堆和栈(stm32 Heap & Stack)【worldsing笔记】
查看>>
STM32 keil mdk启动代码发分析 .
查看>>
解析 STM32 的启动过程(写的不错)
查看>>
应用层和传输层的关系
查看>>
802.11协议用到的简写
查看>>
802.11 学习笔记
查看>>
lwip--有趣的数组定义(预处理)
查看>>
lwIP配置文件opt.h和lwipopts.h初步分析
查看>>
lwIP配置文件opt.h和lwipopts.h初步分析
查看>>
lwIP ARP协议分析
查看>>
智能卡操作系统(COS),什么是智能卡操作系统(COS)
查看>>
基于linux-2.6.38.8内核的SDIO/wifi驱动分析
查看>>
天线 基本概念
查看>>
【经典讨论】STM8L和MSP430的低功耗对比(长期开放)
查看>>
S3C2440、S3C2450和S3C6410之间区别
查看>>
S3C2440和S3C6410性能比较
查看>>
HDMI接口与VGA接口有什么区别?
查看>>
protel99se 定位孔干什么用的,定位孔和安装孔有什么区别?
查看>>