iOS--二维码的扫描

前面我们介绍了二维码的生成,现在我们介绍一下怎么扫描二维码;在iOS7之前,大部分应用中使用的二维码扫描是ZXing或者ZBar第三方的扫描框架。在iOS7之后,苹果自身提供了二维码的扫描功能,从效率上来说,原生的二维码远高于这些第三方框架,现在就看看类似与微信扫一扫的界面的实现;

微信扫一扫

  实现扫描二维码,首先要调起系统摄像头创建视频会话,所以需要导入AVFoundation框架;还要用到以下几个类:

@property (strong,nonatomic)AVCaptureDevice *device;              // 捕捉硬件设备
@property (strong,nonatomic)AVCaptureDeviceInput *input;          // 外界成像输入配置
@property (strong,nonatomic)AVCaptureMetadataOutput *output;      // 成像输出配置
@property (strong,nonatomic)AVCaptureSession *session;            // 捕捉设置
@property (strong,nonatomic)AVCaptureVideoPreviewLayer *prelayer; // 图层布局层

@property (nonatomic, retain) UIImageView *line;                  // 扫描动画线

1、AVCaptureDevice:捕捉硬件设备;
2、AVCaptureDeviceInput: 外界成像输入配置。这个类用来表示输入数据的硬件设备,配置抽象设备的port;
3、AVCaptureMetadataOutput: 成像输出配置。这个支持二维码、条形码等图像数据的识别;
4、AVCaptureSession: 捕捉设置,此类作为硬件设备输入输出信息的桥梁,承担实时获取设备数据的责任;
5、AVCaptureVideoPreviewLayer: 图层类。用来快速呈现摄像头获取的原始数据二维码扫描功能的实现步骤是创建好会话对象,用来获取从硬件设备输入的数据,并实时显示在界面上;

下面是具体实现和创建方法,在创建前先检测下摄像头有没有允许开启:

- (void)setupCamera
{
    AVCaptureDevice *device = [AVCaptureDevice   defaultDeviceWithMediaType:AVMediaTypeVideo];
    if (device==nil){
        UIAlertView * alert = [[UIAlertView alloc]initWithTitle:@"温馨提示" message:@"摄像头未开启" delegate:nil cancelButtonTitle:@"确定" otherButtonTitles:nil, nil];
        [alert show];
        return;
    }
    // Device (获取手机设备的硬件 —— 摄像头)
    _device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
    
    // Input (获取手机摄像头的输入流设置)
    _input = [AVCaptureDeviceInput deviceInputWithDevice:_device error:nil];
    
    // Output (获取手机摄像头的输出流设置)
    _output = [[AVCaptureMetadataOutput alloc]init];
    // 设置输出流协议 AVCaptureMetadataOutputObjectsDelegate
    [_output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
    
    // Session 硬件配置设置
    _session = [[AVCaptureSession alloc]init];
    [_session setSessionPreset:AVCaptureSessionPresetHigh];
    // 加入硬件配置的输入输出流
    if ([_session canAddInput:self.input])
    {
        [_session addInput:self.input];
    }
    
    if ([_session canAddOutput:self.output])
    {
        [_session addOutput:self.output];
    }
    
    // 条码类型 AVMetadataObjectTypeQRCode
    
    if ([self.output.availableMetadataObjectTypes containsObject:AVMetadataObjectTypeQRCode])
    {
        self.output.metadataObjectTypes = @[AVMetadataObjectTypeQRCode,AVMetadataObjectTypeEAN13Code];
    }
    
    // Preview
    dispatch_async(dispatch_get_main_queue(), ^{
        // 更新界面
        _prelayer =[AVCaptureVideoPreviewLayer layerWithSession:self.session];
        _prelayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
        _prelayer.frame = CGRectMake(0, 64, SCREEN_WIDTH, SCREEN_HEIGHT-64);
        [self.view.layer insertSublayer:self.prelayer atIndex:0];
        // Start 开启摄像头运行
        [_session startRunning];
    });
    
}

设置摄像头拍照的范围就是_prelayer.frame这个方法,注意,这是拍照的范围,并不是扫描的范围,要想限制二维码扫描区域,让用户能够准确对准某个二维码,不让整个屏幕都是扫描区域,像微信一样设置区域在框内,需要设置一个参数rectOfInterest ,这个参数有点特别,这个参数的rect 与平常设置的坐标系是完全相反的,原点坐标在右上角,即X与Y互换、W与H互换,这个属性的每一个值取值范围在0~1之间;先设置扫描区域的宽、高以及屏幕宽、高的宏定义:

#define SCREEN_WIDTH        [UIScreen mainScreen].bounds.size.width
#define SCREEN_HEIGHT       [UIScreen mainScreen].bounds.size.height

#define scanWidth           (SCREEN_WIDTH  - 120)
#define scanHeight          scanWidth
#define scanX               (SCREEN_WIDTH - scanWidth)/2
#define scanY               (SCREEN_HEIGHT - scanHeight)/2

给output设置扫描区域:

//设置扫描区域
CGFloat x = scanX/SCREEN_WIDTH;
CGFloat y = scanY/SCREEN_HEIGHT;
CGFloat width = scanWidth/SCREEN_WIDTH;
CGFloat height = scanHeight/SCREEN_HEIGHT;
//y 与 x 互换  width 与 height 互换
[_output setRectOfInterest:CGRectMake(y,x, height, width)];

到这,扫描已经准备完毕,现在实现扫描动画,使线循环上下运动;首先定义一个全局变量,扫描线和定时器:

@property(assign, nonatomic) BOOL                       isUp;
@property(strong, nonatomic) NSTimer                    *timer;

初始化图片框和扫描线:

-(void)initView{
    
    _isUp = NO;
    
    UIImageView * imageView = [[UIImageView alloc]initWithFrame:CGRectMake(scanX, scanY, scanWidth, scanHeight)];
    imageView.image = [UIImage imageNamed:@"watch_sao_kuang"];
    [self.view addSubview:imageView];
    
    _line = [[UIImageView alloc] initWithFrame:CGRectMake(scanX, scanY+5, scanWidth, 2)];
    _line.image = [UIImage imageNamed:@"watch_sao_line"];
    [self.view addSubview:_line];
    
    _timer = [NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:@selector(repeatTime) userInfo:nil repeats:YES];
}

给定时器设置为0.01秒,线移动到上下间隔5个像素,实现动画:

-(void)repeatTime
{

    CGRect rect = _line.frame;
    if (_isUp == NO) {
        
        rect.origin.y += 1;
        
        if (rect.origin.y >= scanY + scanHeight - 5 - rect.size.height)
        {
            _isUp = YES;
        }
    } else {
        rect.origin.y -= 1;
        
        if (rect.origin.y <= scanY + 5)
        {
            _isUp = NO;
        }
    }
    _line.frame = rect;
}

运行效果如下:

效果图

  看看上面的微信扫一扫界面,似乎还差一步,就是非扫描区域和扫描区域颜色有些区分,颜色暗一点,有同学说这个简单,在非扫描区域覆盖上、下、左、右4个View就行,是的,可以这样做,我第一次写项目也是这么做的,不难,就是坐标算起来稍微复杂点,在这里介绍一种更为简单的方法,使用CAShapeLayer动画,CAShapeLayer 是一个通过矢量图形而不是 bitmap 来绘制的图层子类。可以指定颜色、线宽等属性,用CGPath 来定义想要绘制的图形,最后 CAShapeLayer 就会自动渲染出来。主要思路是设置绘制的路径及颜色,然后进行绘画填充,代码如下:

- (void)setshapeLayer{
    
    _shapeLayer = [[CAShapeLayer alloc] init];
    CGMutablePathRef path = CGPathCreateMutable();
    
    //添加蒙版
    CGPathAddRect(path, nil, self.view.bounds);
    
    CGPathAddRect(path, nil, CGRectMake(scanX, scanY, scanWidth, scanHeight));
    
    //填充规则
    _shapeLayer.fillRule = kCAFillRuleEvenOdd;
    //绘制的路径
    _shapeLayer.path = path;
    //路径中的填充颜色
    _shapeLayer.fillColor = [UIColor blackColor].CGColor;
    //设置透明度
    _shapeLayer.opacity = 0.5;
    
    
    [_shapeLayer setNeedsDisplay];
    
    [self.view.layer addSublayer:_shapeLayer];
}

有几个关键属性,就是添加蒙版CGPathAddRect,可以看到我们添加了两次蒙版,为什么呢?因为除了中间的扫描区域之外,其他的全部要置灰,所以要添加两条路径,一条是屏幕大小,还有一条是扫描区域大小。然后将路径赋给CAShapeLayer的path属性;想清楚这个规则,这就要还得看看填充规则属性fillRule,它有两个属性:kCAFillRuleNonZerokCAFillRuleEvenOdd:

  • kCAFillRuleNonZero:默认值,非零规则,当这个点作任意方法的射线,然后看射线和路径的交点方向,选择一个作为基准方向,如果方向一致则加1,方向不一致则减1。为0时,点不在路径内。
  • kCAFillRuleEvenOdd:奇偶规则,当这个点作任意方法的射线,射线和路径的交点数量是奇数则认为点在内部。

我们选择的是kCAFillRuleEvenOdd奇偶规则,意思就是添加蒙版的地方随意找个点画射线,与蒙版路径相交的点数是奇还是偶,奇数代表是内部,偶数代表是外部,内部填充颜色,外部不管。可能你还不是很理解,下面我画了一张图,更加直观:

kCAFillRuleEvenOdd规则

由图可知,1、2代表大框除了小框的部分区域,就是我们的非扫描区域,看出无论从哪个方向画射线,交点数都是基数个,黄点代表焦点数,1有一个交点,2有三个交点;小框内部部分,就是我们的扫描区域,任意画出的射线都是二个交点,如线3;所以CAShapeLayer自动识别,实现了中间的扫描区域之外,其他的全部要置灰;得到的效果图如下:

最终效果图

  所有的准备工作都做完了,现在是验收阶段了,只要我们包含了<AVCaptureMetadataOutputObjectsDelegate>代理就可以通过代理方法获取扫描到的东西了:

#pragma mark AVCaptureMetadataOutputObjectsDelegate
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection
{
    
    NSString *stringValue;
    
    // 元数据对象
    if ([metadataObjects count] >0)
    {
        // 可读码对象
        AVMetadataMachineReadableCodeObject * metadataObject = [metadataObjects objectAtIndex:0];
        stringValue = metadataObject.stringValue;
        
        // 扫描成功后调用,解析二维码
        [_timer setFireDate:[NSDate distantFuture]];
        [_session stopRunning];
        
        NSLog(@"扫一扫结果:%@",stringValue);
        
        UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"扫描结果" message:stringValue preferredStyle:UIAlertControllerStyleAlert];
        [alert addAction:[UIAlertAction actionWithTitle:@"确认" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
            if (_session != nil && _timer != nil) {
                [_session startRunning];
                [_timer setFireDate:[NSDate date]];
            }
            
        }]];
        [self presentViewController:alert animated:YES completion:nil];
    }

}

到这里扫描的页面全部完成了。有几点值得注意的地方,当扫描成功之后,已经执行了[_session stopRunning];所以当我进入下个页面返回或者左滑又松开的时候页面会卡死,想要重新扫描,必须重新开启,还有定时器也需要重新开启,我们可以放在viewWillAppear方法开启:

- (void)viewDidLoad {
    [super viewDidLoad];
    self.title = @"扫一扫";
    self.view.backgroundColor = [UIColor whiteColor];
    
    [self initView];
    
    [self setshapeLayer];
    
    [self setupCamera];
    
}
-(void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    
    [_timer setFireDate:[NSDate date]];
    [_session startRunning];
    
}
-(void)viewDidDisappear:(BOOL)animated{
    [super viewDidDisappear:animated];
    
    [_session stopRunning];
}

声明: 转载请注明出处http://www.jianshu.com/p/34c66cd69df9

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容

  • // 3. 添加输出设备 pragma marke 扫描的代理方法 实现这个代理方法,在这里面获取扫描的结果...
    cj小牛阅读 545评论 0 5
  • 关于二维码(或者条形码,以下归类简称二维码)扫描和生成的,我相信网络上相关的文章层数不穷,但是,大部分都是直接粘贴...
    FR_Zhang阅读 6,583评论 10 41
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,580评论 18 139
  • 简介 二维条码/二维码是用某种特定的几何图形按一定规律在平面分布的黑白相间的图形记录数据符号信息的 在编码上巧妙地...
    论丶道阅读 2,497评论 3 7
  • 是的,优雅得形容别人很胖有很多种方法, 但是我们胖子也是有尊严的好吗! 下次记得要反击: .......... 经...
    自愈学院阅读 329评论 0 2