美文网首页iOS 开发iOS技术帖iOS OC 学习手册
iOS二维码扫描(原生,可限制扫描区域)

iOS二维码扫描(原生,可限制扫描区域)

作者: 船长_ | 来源:发表于2015-12-09 23:06 被阅读8556次

    须知:

    • 在 iOS7 以前,在iOS中实现二维码和条形码扫描,我们所知的有,两大开源组件ZBar与ZXing. 这两大组件我们都有用过,这里总结下各自的缺点:
    • 1 .ZBar在扫描的灵敏度上,和内存的使用上相对于ZXing上都是较优的,但是对于 “圆角二维码” 的扫描确很困难
    • 2 .ZXing 是 Google Code上的一个开源的条形码扫描库,是用java设计的,连Google Glass 都在使用的。但有人为了追求更高效率以及可移植性,出现了c++ port. Github上的Objectivc-C port,其实就是用OC代码封装了一下而已,而且已经停止维护。这样效率非常低,在instrument下面可以看到CPU和内存疯涨,在内存小的机器上很容易崩溃
    • 3 .AVFoundation无论在扫描灵敏度和性能上来说都是最优的,所以毫无疑问我们应该切换到AVFoundation,需要兼容iOS 6或之前的版本可以用zbar或zxing代替

    0.搭建UI界面,如图

    Snip20151209_1.png

    1.导入框架

    #import <AVFoundation/AVFoundation.h>
    

    2.声明属性,连线,设置需要的代理

    @interface ViewController ()<UITabBarDelegate,AVCaptureMetadataOutputObjectsDelegate,UINavigationControllerDelegate,UIImagePickerControllerDelegate>
    
    @property ( strong , nonatomic ) AVCaptureDevice * device;
    @property ( strong , nonatomic ) AVCaptureDeviceInput * input;
    @property ( strong , nonatomic ) AVCaptureMetadataOutput * output;
    @property ( strong , nonatomic ) AVCaptureSession * session;
    @property ( strong , nonatomic ) AVCaptureVideoPreviewLayer * previewLayer;
    
    /*** 专门用于保存描边的图层 ***/
    @property (nonatomic,strong) CALayer *containerLayer;
    @end
    

    3.调用开始扫描设置方法

    - (void)viewDidLoad {
        [super viewDidLoad];
    
        // 开始扫描二维码
        [self startScan];
    }
    

    4.属性的懒加载

    #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;
    }
    
    - (AVCaptureSession *)session
    {
        if (_session == nil) {
            _session = [[AVCaptureSession alloc] init];
        }
        return _session;
    }
    
    - (AVCaptureVideoPreviewLayer *)previewLayer
    {
        if (_previewLayer == nil) {
            _previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:self.session];
        }
        return _previewLayer;
    }
    // 设置输出对象解析数据时感兴趣的范围
    // 默认值是 CGRect(x: 0, y: 0, width: 1, height: 1)
    // 通过对这个值的观察, 我们发现传入的是比例
    // 注意: 参照是以横屏的左上角作为, 而不是以竖屏
    //        out.rectOfInterest = CGRect(x: 0, y: 0, width: 0.5, height: 0.5)
    - (AVCaptureMetadataOutput *)output
    {
        if (_output == nil) {
            _output = [[AVCaptureMetadataOutput alloc] init];
            
            // 1.获取屏幕的frame
            CGRect viewRect = self.view.frame;
            // 2.获取扫描容器的frame
            CGRect containerRect = self.customContainerView.frame;
            
            CGFloat x = containerRect.origin.y / viewRect.size.height;
            CGFloat y = containerRect.origin.x / viewRect.size.width;
            CGFloat width = containerRect.size.height / viewRect.size.height;
            CGFloat height = containerRect.size.width / viewRect.size.width;
            
           // CGRect outRect = CGRectMake(x, y, width, height);
           // [_output rectForMetadataOutputRectOfInterest:outRect];
            _output.rectOfInterest = CGRectMake(x, y, width, height);
        }
        return _output;
    }
    
    - (CALayer *)containerLayer
    {
        if (_containerLayer == nil) {
            _containerLayer = [[CALayer alloc] init];
        }
        return _containerLayer;
    }
    

    5.开始扫描

    - (void)startScan
    {
        // 1.判断输入能否添加到会话中
        if (![self.session canAddInput:self.input]) return;
        [self.session addInput:self.input];
    
        
        // 2.判断输出能够添加到会话中
        if (![self.session canAddOutput:self.output]) return;
        [self.session addOutput:self.output];
        
        // 4.设置输出能够解析的数据类型
        // 注意点: 设置数据类型一定要在输出对象添加到会话之后才能设置
        self.output.metadataObjectTypes = self.output.availableMetadataObjectTypes;
    
        // 5.设置监听监听输出解析到的数据
        [self.output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
     
        // 6.添加预览图层
        [self.view.layer insertSublayer:self.previewLayer atIndex:0];
        self.previewLayer.frame = self.view.bounds;
        
        // 7.添加容器图层
        [self.view.layer addSublayer:self.containerLayer];
        self.containerLayer.frame = self.view.bounds;
        
        // 8.开始扫描
        [self.session startRunning];
    }
    

    6.实现代理方法

    #pragma mark --------AVCaptureMetadataOutputObjectsDelegate ---------
    - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection
    {
            // id 类型不能点语法,所以要先去取出数组中对象
            AVMetadataMachineReadableCodeObject *object = [metadataObjects lastObject];
            
            if (object == nil) return;
            // 只要扫描到结果就会调用
            self.customLabel.text = object.stringValue;
          
            // 清除之前的描边
            [self clearLayers];
            
            // 对扫描到的二维码进行描边
            AVMetadataMachineReadableCodeObject *obj = (AVMetadataMachineReadableCodeObject *)[self.previewLayer transformedMetadataObjectForMetadataObject:object];
    
            // 绘制描边
            [self drawLine:obj];
    }
    

    7.利用贝塞尔曲线绘制描边

    - (void)drawLine:(AVMetadataMachineReadableCodeObject *)objc
    {
        NSArray *array = objc.corners;
        
        // 1.创建形状图层, 用于保存绘制的矩形
        CAShapeLayer *layer = [[CAShapeLayer alloc] init];
    
        // 设置线宽
        layer.lineWidth = 2;
        // 设置描边颜色
        layer.strokeColor = [UIColor greenColor].CGColor;
        layer.fillColor = [UIColor clearColor].CGColor;
    
        // 2.创建UIBezierPath, 绘制矩形
        UIBezierPath *path = [[UIBezierPath alloc] init];
        CGPoint point = CGPointZero;
        int index = 0;
        
        CFDictionaryRef dict = (__bridge CFDictionaryRef)(array[index++]);
        // 把点转换为不可变字典
        // 把字典转换为点,存在point里,成功返回true 其他false
        CGPointMakeWithDictionaryRepresentation(dict, &point);
        
       // 设置起点
        [path moveToPoint:point];
        
        // 2.2连接其它线段
        for (int i = 1; i<array.count; i++) {
            CGPointMakeWithDictionaryRepresentation((__bridge CFDictionaryRef)array[i], &point);
            [path addLineToPoint:point];
        }
        // 2.3关闭路径
        [path closePath];
        
        layer.path = path.CGPath;
        // 3.将用于保存矩形的图层添加到界面上
        [self.containerLayer addSublayer:layer];
    }
    

    8.清除描边

    - (void)clearLayers
    {
        if (self.containerLayer.sublayers)
        {
            for (CALayer *subLayer in self.containerLayer.sublayers)
            {
                [subLayer removeFromSuperlayer];
            }
        }
    }
    

    实现相册二维码识别

    1.打开系统相册

    - (IBAction)openCameralClick:(id)sender {
        // 1.判断相册是否可以打开
        if (![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary]) return;
        // 2. 创建图片选择控制器
        UIImagePickerController *ipc = [[UIImagePickerController alloc] init];
        
        ipc.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
        
        // 4.设置代理
        ipc.delegate = self;
        
        // 5.modal出这个控制器
        [self presentViewController:ipc animated:YES completion:nil];
    }
    

    2.实现代理方法(注意需要遵守两个代理协议)

    #pragma mark -------- UIImagePickerControllerDelegate---------
    - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info
    {
        // 1.取出选中的图片
        UIImage *pickImage = info[UIImagePickerControllerOriginalImage];
        NSData *imageData = UIImagePNGRepresentation(pickImage);
    
        CIImage *ciImage = [CIImage imageWithData:imageData];
        
        // 2.从选中的图片中读取二维码数据
        // 2.1创建一个探测器
        CIDetector *detector = [CIDetector detectorOfType:CIDetectorTypeQRCode context:nil options:@{CIDetectorAccuracy: CIDetectorAccuracyLow}];
        
        // 2.2利用探测器探测数据
        NSArray *feature = [detector featuresInImage:ciImage];
    
        // 2.3取出探测到的数据
        for (CIQRCodeFeature *result in feature) {
           // NSLog(@"%@",result.messageString);
            NSString *urlStr = result.messageString;
            [[UIApplication sharedApplication] openURL:[NSURL URLWithString:urlStr]];
        }
        
        // 注意: 如果实现了该方法, 当选中一张图片时系统就不会自动关闭相册控制器
        [picker dismissViewControllerAnimated:YES completion:nil];
    }
    

    3.在界面消失的时候关闭session

    - (void)viewWillDisappear:(BOOL)animated
    {
        [super viewWillDisappear:animated];
        [self.session stopRunning];
    }
    

    效果图:

    二维码.PNG

    相关文章

      网友评论

      • f9c0cb638fab:为什么 我真机运行的时候 相机扫描的透明区域 不能全屏?
        f9c0cb638fab:@船长_ 解决了 谢谢
        船长_:@或早 很明显frame不对
      • CoderSJun:楼主,我把扫码方法封装了一个工具类 死活不走 扫码的代理方法,设置代理那句话也走了, 代码一样,放在controller 就又走了 。可能会是什么原因啊。
      • Zz7777777:支持ios7吗
      • 西叶lv:有Demo么楼主??有效扫描区四角上的边框怎么整???
      • ThaiLanKing:NSMutableArray *supportTypes = [self.output.availableMetadataObjectTypes mutableCopy];
        [supportTypes removeObject:AVMetadataObjectTypeFace];
        self.output.metadataObjectTypes = [supportTypes copy];

        需要筛选一下支持的类型,不然会闪退,faceType没有stringValue
        Zz7777777:#pragma mark --------AVCaptureMetadataOutputObjectsDelegate ---------
        - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection
        {

        // if (metadataObjects.count > 0) {
        // id 类型不能点语法,所以要先去取出数组中对象

        NSMutableArray *supportTypes = [self.output.availableMetadataObjectTypes mutableCopy];
        [supportTypes removeObject:AVMetadataObjectTypeFace];
        self.output.metadataObjectTypes = [supportTypes copy];

        AVMetadataMachineReadableCodeObject *object = [metadataObjects lastObject];

        if (object == nil) return;
        // 只要扫描到结果就会调用
        self.customLabel.text = object.stringValue;

        [self clearLayers];
        Zz7777777:@HelloCoders 支持iOS7的吗
        Zz7777777:大神 在哪里进行筛选 你测过这个识别ios7的
      • 猿猴:为什么rectOfInterest 我设置成0,0,1,1 它却不是全屏扫描?
      • Link913:能扫条形码吗
        船长_:@SkyHarute 你试试
      • 小蜗牛吱呀之悠悠:好文章,谢谢楼主
      • iOS_渔翁:能请教一些吗?
        船长_:可以啊
      • iOS_渔翁:想请教一个问题啊
      • iOS_渔翁:你好 我的还有点bug,能加QQ请教一下吗?273911162
      • 6add93d66689:你好,含有中文信息,扫描不出来.
      • e14dcf1ff587:您好楼主,请问有没有关于iOS 条形码扫描的demo,不是二维码的。
        船长_:https://github.com/dongxiexidu/QR-Code.git
      • 夏日里的夏天:为什么进入相处选择照片之后就没有然后了 没有显示出来的
      • 薛定谔的熊:iPhone5实测进入相册选择图片然后就没有然后了
      • 春田花花幼儿园:原声二维码扫的一溜一溜的,主要是条形码识别不好,不能支持所有的格式.不知道作者您有没有做过用原生扫描条形码
      • 哈哈我来了:iphone5及以下手机不能用从相册读取二维码,但是可以扫描
      • _誌念:赞,二维码扫描
      • 04bae10af0d1:大神, 给个 Demo 地址可以不?
        04bae10af0d1:@船长_ 谢谢👍
        船长_:@iosfxi https://github.com/dongxiexidu/QR-Code.git
      • ningning_:相册识别二维码6 以上的机器都不可
      • 我的天空蔚蓝色:这么好的文章为毛没人顶呢,妹的
        小小夕舞:你该不会是作者小号吧 已赞 哈哈哈
      • 叶舞清风:不仅可以扫描二维码也可以生成二维码
      • 从此你不再颠沛流离:还是imageview??
        b06be3a52edb:customContainerView 加到谁的上面
        叶舞清风:@船长_ 不是当前view吗
        船长_:@从此你不再颠沛流离 UIView
      • 从此你不再颠沛流离:你好,请问你定义的customContainerView是普通的view么
      • onces:不错,顶一个 :smile:

      本文标题:iOS二维码扫描(原生,可限制扫描区域)

      本文链接:https://www.haomeiwen.com/subject/ywxyhttx.html