iOS之二维码扫描

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

这里写图片描述

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

首先说一下二维码扫描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试试。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值