iOS开发之扫描二维码

简介: 自iOS7以后,iOS扫描二维码不需要借助于第三方框架了,苹果在AVFoundation中原生支持了扫描二维码的API,主要涉及到5个类,这5个类在自定义相机或者视频时也用得上,网上有很多介绍,这5个类分别为:AVCaptureSession:媒体捕获会话,负责把捕获的音视频数据输出到输出设备中。

自iOS7以后,iOS扫描二维码不需要借助于第三方框架了,苹果在AVFoundation中原生支持了扫描二维码的API,主要涉及到5个类,这5个类在自定义相机或者视频时也用得上,网上有很多介绍,这5个类分别为:

AVCaptureSession:媒体捕获会话,负责把捕获的音视频数据输出到输出设备中。

AVCaptureDevice:输入设备,如麦克风、摄像头。

AVCaptureDeviceInput:设备输入数据管理对象,可以根据AVCaptureDevice创建对应的AVCaptureDeviceInput对象,该对象将会被添加到AVCaptureSession中管理。

AVCaptureOutput:输出数据管理对象,用于接收各类输出数据,有很多子类,每个子类用途都不一样,该对象将会被添加到AVCaptureSession中管理。

AVCaptureVideoPreviewLayer:相机拍摄预览图层,是CALayer的子类,使用该对象可以实时查看拍照或视频录制效果,设置好尺寸后需要添加到父view的layer中。

我在参考了网上的很多博客并自己摸索了以后,写了一个具体的实现案例,过程中遇到很多坑,在此记录并分享一下。

运行环境:Xcode 8.3.2 + iOS 8. 4真机、iOS 10.3.1真机

核心步骤:

1、创建AVCaptureSession会话
2、创建AVCaptureDevice设备
3、创建输入AVCaptureDeviceInput与输出设备AVCaptureMetadataOutput,并添加到上面的会话中
4、创建预览层
5、设置扫描区域

实现

从上面的描述看,除了预览层,其他的和UI界面似乎没什么关系,但是实际开发中,扫描界面一般都是设计的比较人性化的,如支付宝、微信等,中间都有一个小框,有个线上下扫,这个其实就是用UI来配合扫描二维码,给用户一种好的体验。

界面布局

img_90671780bde27450f9bdf2fc9fdf22a7.png
界面布局.png

主要代码

#import "ViewController.h"
#import <AVFoundation/AVFoundation.h>

@interface ViewController () <AVCaptureMetadataOutputObjectsDelegate, CALayerDelegate>

/**
 *  UI
 */
@property (weak, nonatomic) IBOutlet UIView *scanView;
@property (weak, nonatomic) IBOutlet UIImageView *scanline;
@property (weak, nonatomic) IBOutlet UILabel *result;

/**
 *  扫描区域的高度约束值(宽度一致)
 */
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *scanViewH;
/**
 *  扫描线的顶部约束值
 */
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *scanlineTop;
/**
 *  扫描线的高度
 */
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *scanlineH;
@property(nonatomic, strong) CALayer *maskLayer;

/**
 *  五个类
 */
@property(nonatomic, strong) AVCaptureDevice *device;

@property(nonatomic, strong)  AVCaptureDeviceInput *input;

@property(nonatomic, strong)  AVCaptureMetadataOutput *output;

@property(nonatomic, strong) AVCaptureSession *session;

@property(nonatomic, strong) AVCaptureVideoPreviewLayer *layer;

@end

@implementation ViewController

#pragma mark - 懒加载

-(AVCaptureDevice *)device{
    if (_device == nil) {
        
        _device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
        
    }
    return _device;
    
}

-(AVCaptureDeviceInput *)input{
    
    if (_input == nil) {
        
        _input = [AVCaptureDeviceInput deviceInputWithDevice:self.device error:nil];
        
    }
    
    return  _input;
    
}

-(AVCaptureMetadataOutput *)output{
    
    if (_output == nil) {
        
        _output = [[AVCaptureMetadataOutput alloc]init];
        
        [_output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
        
    }
    
    return  _output;
}

#pragma mark - ViewController生命周期
/**
 *  执行扫描动画
 */
-(void)viewDidAppear:(BOOL)animated{
    
    [super viewDidAppear:animated];
    
    [self startAnim];
    
}

/**
 *  注册进入前台通知 保证下次进来还有扫描动画
 */
-(void)viewWillAppear:(BOOL)animated{
    
    [super viewWillAppear:animated];

    //注册程序进入前台通知
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector (startAnim) name: UIApplicationWillEnterForegroundNotification object:nil];
}

/**
 *  移除通知
 */
-(void)viewWillDisappear:(BOOL)animated{
    
    [super viewWillDisappear:animated];
    //解除程序进入前台通知
    [[NSNotificationCenter defaultCenter] removeObserver:self name: UIApplicationWillEnterForegroundNotification object:nil];
     
     
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    
    //1、创建会话
    AVCaptureSession *session = [[AVCaptureSession alloc]init];
    
    if ([session canSetSessionPreset:AVCaptureSessionPresetHigh]) {
        
        [session setSessionPreset:AVCaptureSessionPresetHigh];
        
    }
    
    //2、添加输入和输出设备
    if([session canAddInput:self.input]){
        
        [session addInput:self.input];
        
    }
    if([session canAddOutput:self.output]){
        
        [session addOutput:self.output];
    }
    
    
    //3、设置这次扫描的数据类型
    self.output.metadataObjectTypes = self.output.availableMetadataObjectTypes;
    
    //4、创建预览层
    AVCaptureVideoPreviewLayer *layer = [AVCaptureVideoPreviewLayer layerWithSession:session];
    
    layer.frame = self.view.bounds;
    
    [self.view.layer insertSublayer:layer atIndex:0];
    
    
    //5、创建周围的遮罩层
    CALayer *maskLayer = [[CALayer alloc]init];
    
    maskLayer.frame = self.view.bounds;
    
    //此时设置的颜色就是中间扫描区域最终的颜色
    maskLayer.backgroundColor = [UIColor colorWithRed:0.1 green:0.1 blue:0.1 alpha:0.2].CGColor;
    
    maskLayer.delegate = self;
    
    [self.view.layer insertSublayer:maskLayer above:layer];
    
    //让代理方法调用 将周围的蒙版颜色加深
    [maskLayer setNeedsDisplay];
    
    
    //6、关键设置扫描的区域 方法一:自己计算
    //    CGFloat x = (self.view.bounds.size.width - self.scanViewH.constant) * 0.5;
    //
    //    CGFloat y = (self.view.bounds.size.height- self.scanViewH.constant) * 0.5;
    //
    //    CGFloat w = self.scanViewH.constant;
    //
    //    CGFloat h = w;
    //
    //
    //    self.output.rectOfInterest = CGRectMake(y/self.view.bounds.size.height, x/self.view.bounds.size.width,  h/self.view.bounds.size.height, w/self.view.bounds.size.width);
    
    //6、关键设置扫描的区域,方法二:直接转换,但是要在 AVCaptureInputPortFormatDescriptionDidChangeNotification 通知里设置,否则 metadataOutputRectOfInterestForRect: 转换方法会返回 (0, 0, 0, 0)。
    
    __weak __typeof(&*self)weakSelf = self;
    
    [[NSNotificationCenter defaultCenter] addObserverForName:AVCaptureInputPortFormatDescriptionDidChangeNotification object:nil queue:[NSOperationQueue currentQueue] usingBlock: ^(NSNotification *_Nonnull note) {
        
        weakSelf.output.rectOfInterest = [weakSelf.layer metadataOutputRectOfInterestForRect:self.scanView.frame];
        
    }];
    
    
    //7、开始扫描
    [session startRunning];
    
    
    self.session = session;
    self.layer = layer;
    self.maskLayer = maskLayer;
}

-(void)dealloc{
    
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    
}


#pragma mark - 代理方法
/**
 *  如果扫描到了二维码 回调该代理方法
 */
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection{
    
    if(metadataObjects.count > 0 && metadataObjects != nil){
        
        
        AVMetadataMachineReadableCodeObject *metadataObject = [metadataObjects lastObject];
        
        NSString *result = metadataObject.stringValue;
        
        self.result.text = result;
        
        [self.session stopRunning];
        
        [self.scanline removeFromSuperview];
        
    }
    
    
}

/**
 *   蒙版中间一块要空出来
 */

-(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx{
    
    if (layer == self.maskLayer) {
        
        UIGraphicsBeginImageContextWithOptions(self.maskLayer.frame.size, NO, 1.0);
        
        //蒙版新颜色
        CGContextSetFillColorWithColor(ctx, [UIColor colorWithRed:0.1 green:0.1 blue:0.1 alpha:0.8].CGColor);
        
        CGContextFillRect(ctx, self.maskLayer.frame);
        
        //转换坐标
        CGRect scanFrame = [self.view convertRect:self.scanView.frame fromView:self.scanView.superview];
        
        //空出中间一块
        CGContextClearRect(ctx, scanFrame);
    }
}

@end

#pragma mark - 自定义方法
/**
 *  扫描的那条线动起来
 */
-(void)startAnim{
    
    //如果是第二次进来 那么动画已经执行完毕 要重新开始动画的话 必须让约束归位
    if(self.scanlineTop.constant == self.scanViewH.constant - 4){
    
        self.scanlineTop.constant -= self.scanViewH.constant - 4;
    
        [self.view layoutIfNeeded];
    
    }
    
    //执行动画
    [UIView animateWithDuration:3.0 delay:0 options:UIViewAnimationOptionRepeat animations:^{

        self.scanlineTop.constant = self.scanViewH.constant - 4;
        
        [self.view layoutIfNeeded];
        
        
    } completion:nil];
    
}

Info.plist中 不同iOS版本需要添加相应的权限

最终效果

img_cceddd83d11f3de15fd9e1d2197e51a4.gif
扫描二维码.gif

总结

一、遇到的坑

1、设置了AutoLayout,想要做动画,这时候动画放在viewDidAppear中执行,并且不要用bounds,frame来改变动画,要用具体的约束,但是直接在UIView动画中修改约束是没效果的,需要在设置完约束以后,加上[self.view layoutIfNeeded];

2、设置扫描区域,也就是设置AVCaptureMetadataOutputrectOfInterest属性,它是一个CGRect类型,但是它的四个值和传统的不一样,是(y,x,高,宽)且是比例值,取值范围为0~1。那么有两种方案,第一种需要自己计算具体位置的比例,如代码中注释的那些。第二种方案用AVCaptureVideoPreviewLayermetadataOutputRectOfInterestForRect方法,但是直接设置是没有效果的,必须放到通知里,如文中所示。

3、中间方块是通过CALayer两步实现的,第一步设置整个背景颜色,这个颜色根据中间想显示的样式来设置;第二步在代理方法里面重新设置一次背景颜色,这个颜色根据除中间以外的区域来设置,然后将中间的挖掉。但是必须调用setNeedsDisplay方法,否则代理方法不会调用。

二、参考文献

1、iOS开发系列--音频播放、录音、视频播放、拍照、视频录制
2、iOS开发 - 二维码的扫描
3、iOS二维码扫描与生成(优化启动卡顿)

三、源代码

目录
相关文章
|
4天前
|
安全 数据处理 Swift
深入探索iOS开发中的Swift语言特性
本文旨在为开发者提供对Swift语言在iOS平台开发的深度理解,涵盖从基础语法到高级特性的全面分析。通过具体案例和代码示例,揭示Swift如何简化编程过程、提高代码效率,并促进iOS应用的创新。文章不仅适合初学者作为入门指南,也适合有经验的开发者深化对Swift语言的认识。
19 9
|
3天前
|
Android开发 Swift iOS开发
探索安卓与iOS开发的差异和挑战
【10月更文挑战第37天】在移动应用开发的广阔舞台上,安卓和iOS这两大操作系统扮演着主角。它们各自拥有独特的特性、优势以及面临的开发挑战。本文将深入探讨这两个平台在开发过程中的主要差异,从编程语言到用户界面设计,再到市场分布的不同影响,旨在为开发者提供一个全面的视角,帮助他们更好地理解并应对在不同平台上进行应用开发时可能遇到的难题和机遇。
|
2天前
|
iOS开发 开发者
探索iOS开发中的SwiftUI框架
【10月更文挑战第39天】在苹果的生态系统中,SwiftUI框架以其声明式语法和易用性成为开发者的新宠。本文将深入SwiftUI的核心概念,通过实际案例展示如何利用这一框架快速构建用户界面,并探讨其对iOS应用开发流程的影响。
|
5天前
|
JSON 前端开发 API
探索iOS开发之旅:打造你的第一个天气应用
【10月更文挑战第36天】在这篇文章中,我们将踏上一段激动人心的旅程,一起构建属于我们自己的iOS天气应用。通过这个实战项目,你将学习到如何从零开始搭建一个iOS应用,掌握基本的用户界面设计、网络请求处理以及数据解析等核心技能。无论你是编程新手还是希望扩展你的iOS开发技能,这个项目都将为你提供宝贵的实践经验。准备好了吗?让我们开始吧!
|
9天前
|
设计模式 前端开发 Swift
探索iOS开发:从初级到高级的旅程
【10月更文挑战第31天】在这篇文章中,我们将一起踏上iOS开发的旅程。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的信息和技巧。我们将从基础开始,逐步深入到更高级的技术和概念。让我们一起探索iOS开发的世界吧!
|
12天前
|
设计模式 前端开发 Swift
探索iOS开发:从初级到高级的旅程
【10月更文挑战第28天】在这篇技术性文章中,我们将一起踏上一段探索iOS开发的旅程。无论你是刚入门的新手,还是希望提升技能的开发者,这篇文章都将为你提供宝贵的指导和灵感。我们将从基础概念开始,逐步深入到高级主题,如设计模式、性能优化等。通过阅读这篇文章,你将获得一个清晰的学习路径,帮助你在iOS开发领域不断成长。
38 2
|
18天前
|
安全 API Swift
探索iOS开发中的Swift语言之美
【10月更文挑战第23天】在数字时代的浪潮中,iOS开发如同一艘航船,而Swift语言则是推动这艘船前进的风帆。本文将带你领略Swift的独特魅力,从语法到设计哲学,再到实际应用案例,我们将一步步深入这个现代编程语言的世界。你将发现,Swift不仅仅是一种编程语言,它是苹果生态系统中的一个创新工具,它让iOS开发变得更加高效、安全和有趣。让我们一起启航,探索Swift的奥秘,感受编程的乐趣。
|
20天前
|
Swift iOS开发 开发者
探索iOS开发中的SwiftUI框架
【10月更文挑战第21天】在苹果生态系统中,SwiftUI的引入无疑为iOS应用开发带来了革命性的变化。本文将通过深入浅出的方式,带领读者了解SwiftUI的基本概念、核心优势以及如何在实际项目中运用这一框架。我们将从一个简单的例子开始,逐步深入到更复杂的应用场景,让初学者能够快速上手,同时也为有经验的开发者提供一些深度使用的技巧和策略。
43 1
|
8天前
|
存储 数据可视化 Swift
探索iOS开发之旅:从新手到专家
【10月更文挑战第33天】在这篇文章中,我们将一起踏上一场激动人心的iOS开发之旅。无论你是刚刚入门的新手,还是已经有一定经验的开发者,这篇文章都将为你提供宝贵的知识和技能。我们将从基础的iOS开发概念开始,逐步深入到更复杂的主题,如用户界面设计、数据存储和网络编程等。通过阅读这篇文章,你将获得成为一名优秀iOS开发者所需的全面技能和知识。让我们一起开始吧!
|
9天前
|
移动开发 Java Android开发
探索Android与iOS开发的差异性与互联性
【10月更文挑战第32天】在移动开发的大潮中,Android和iOS两大平台各领风骚。本文将深入浅出地探讨这两个平台的开发差异,并通过实际代码示例,展示如何在各自平台上实现相似的功能。我们将从开发环境、编程语言、用户界面设计、性能优化等多个角度进行对比分析,旨在为开发者提供跨平台开发的实用指南。
29 0