苹果原生二维码的功能确实很强大,则没有这个

2 input 创建输入流

图片 1

序言

在iOS7之前二维码扫描主要是用的第三方库,如ZXing或者ZBar。使用起来比较麻烦,出错也不容易调试。iOS7之后,苹果自身提供了二维码的扫描、生成功能,从效率上来说,原生的二维码远高于这些第三方框架。这里主要分享一下swift版二维码的扫描、生成以及识别图片中的二维码。这里是一个完整的demo。

[_session setSessionPreset:AVCaptureSessionPresetHigh];

3).创建输出设备AVCaptureMetadataOutput

AVCaptureMetadataOutput对象拦截元数据对象发出的相关捕获连接,并将它们转发给委托对象进行处理。您可以使用这个类的实例来处理特定类型的元数据中包含的输入数据。你使用这个类你做其他的输出对象的方式,通常是通过添加一个AVCaptureSession对象作为输出。简单而言就是,AVCaptureMetadataOutput将获取到的元数据交给AVCaptureSession进行处理的途径.

@property (strong,nonatomic)AVCaptureMetadataOutput * output;
  • 对于扫描结果你可以自己处理,包括判断是字符串还是网页链接等等

4. scanCrop

另一个提升扫描速度和性能的就是设置解析的范围,AVFoundation中设置 AVCaptureMetadataOutput 的 rectOfInterest 属性来配置解析范围。

最开始我按照文档说的按照比例值来设置这个属性,如下:

CGSize size = self.view.bounds.size;
CGRect cropRect = CGRectMake(40, 100, 240, 240);
captureOutput.rectOfInterest = CGRectMake(cropRect.origin.x/size.width,
                                         cropRect.origin.y/size.height,
                                         cropRect.size.width/size.width,
                                         cropRect.size.height/size.height);

但是发现 好像不对啊,扫不到了,明显不正确呢,于是猜想: AVCapture输出的图片大小都是横着的,而iPhone的屏幕是竖着的,那么我把它旋转90°呢:

CGSize size = self.view.bounds.size;
CGRect cropRect = CGRectMake(40, 100, 240, 240);
captureOutput.rectOfInterest = CGRectMake(cropRect.origin.y/size.height,
                                         cropRect.origin.x/size.width,
                                         cropRect.size.height/size.height,
                                         cropRect.size.width/size.width);

OK,貌似对了,在iPhone5上一切工作良好,但是在4s上,或者换了sessionPreset的大小之后,这个框貌似就不那么准确了, 可能发现超出框上下一些也是可以扫描出来的。 再次猜想: 图片的长宽比和手机屏幕不是一样的,这个rectOfInterest是相对于图片大小的比例。比如iPhone4s屏幕大小是 640x960, 而图片输出大小是 1920x1080. 实际的情况可能就是下图中的效果:

图片 2

1419476288149730.png

上图中下面的代表iPhone4s屏幕,大小640x960, 上面代表AVCaptureVideoPreviewLayer中预览到的图片位置,在图片输入为1920x1080大小时,实际大小上下会被截取一点的,因为我们AVCaptureVideoPreviewLayer设置的videoGravity是AVLayerVideoGravityResizeAspectFill, 类似于UIView的UIViewContentModeScaleAspectFill效果。

于是我对大小做了一下修正:

CGSize size = self.view.bounds.size;
CGRect cropRect = CGRectMake(40, 100, 240, 240);
CGFloat p1 = size.height/size.width;
CGFloat p2 = 1920./1080.;  //使用了1080p的图像输出
if (p1 < p2) {
  CGFloat fixHeight = bounds.size.width * 1920. / 1080.;
  CGFloat fixPadding = (fixHeight - size.height)/2;
  captureOutput.rectOfInterest = CGRectMake((cropRect.origin.y + fixPadding)/fixHeight,
                                              cropRect.origin.x/size.width,
                                              cropRect.size.height/fixHeight,
                                              cropRect.size.width/size.width);
} else {
    CGFloat fixWidth = bounds.size.height * 1080. / 1920.;
    CGFloat fixPadding = (fixWidth - size.width)/2;
    captureOutput.rectOfInterest = CGRectMake(cropRect.origin.y/size.height,
                                              (cropRect.origin.x + fixPadding)/fixWidth,
                                              cropRect.size.height/size.height,
                                              cropRect.size.width/fixWidth);
}

经过上面的验证,证实了猜想rectOfInterest是基于图像的大小裁剪的。

二维码扫描

二维码扫描主要用AVFoundationAVFoundation是一个很大基础库,用来创建基于时间的视听媒体,可以使用它来检查,创建、编辑或媒体文件。也可以输入流从设备和操作视频实时捕捉和回放。
主要成员介绍:
AVCaptureSession 管理输入(AVCaptureInput)和输出(AVCaptureOutput)流,包含开启和停止会话方法。
AVCaptureDeviceInputAVCaptureInput的子类,可以作为输入捕获会话,用AVCaptureDevice实例初始化。
AVCaptureDevice代表了物理捕获设备如:摄像机。用于配置等底层硬件设置相机的自动对焦模式。
AVCaptureMetadataOutputAVCaptureOutput的子类,处理输出捕获会话。捕获的对象传递给一个委托实现AVCaptureMetadataOutputObjectsDelegate协议。协议方法在指定的派发队列(dispatch queue)上执行。
AVCaptureVideoPreviewLayerCALayer的一个子类,显示捕获到的相机输出流。

图片 3

扫一扫.jpeg

import AVFoundation

 let session = AVCaptureSession()
var layer: AVCaptureVideoPreviewLayer?```

创建会话,读取流:

private func scanQRCode(){
if !isCameraAvailable(){
showAlert("请使用真机", message: "您的设备没有相机.", VC: self)
return
}
self.session.sessionPreset = AVCaptureSessionPresetHigh
do{
let input = try AVCaptureDeviceInput(device: self.device)
if self.session.canAddInput(input){
self.session.addInput(input)
}
}
catch{
showAlert("错误", message: "初始化失败", VC: self)
return
}
self.layer = AVCaptureVideoPreviewLayer(session: self.session)
self.layer?.videoGravity = AVLayerVideoGravityResizeAspectFill
self.layer?.frame = self.view.frame
self.view.layer.addSublayer(self.layer!)
self.session.addObserver(self, forKeyPath: "running", options: .new, context: nil)
if AVCaptureDevice.authorizationStatus(forMediaType: AVMediaTypeVideo) == .authorized{
let output = AVCaptureMetadataOutput()
output.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
//设置扫描的有效区域
output.rectOfInterest = CGRect(x: 140.0/SCREEN_HEIGHT, y: 40.0/SCREEN_WIDTH, width: (SCREEN_WIDTH-80)/SCREEN_HEIGHT, height: (SCREEN_WIDTH-80)/SCREEN_WIDTH)
if self.session.canAddOutput(output) {
self.session.addOutput(output)
output.metadataObjectTypes = [AVMetadataObjectTypeQRCode]
}
self.setOverlayPickerView()
self.session.startRunning()
}else{
showAlert("提示", message: "Authorization is required to use the camera, please check your permission settings: Settings> Privacy> Camera", VC: self)
}
}

获取捕获数据:

//二维码扫描代理
func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [Any]!, from connection: AVCaptureConnection!) {
var stringValue:String?
if metadataObjects.count > 0 {
let metadataObject = metadataObjects[0] as! AVMetadataMachineReadableCodeObject
stringValue = metadataObject.stringValue

    }
    self.session.stopRunning()
    print("code is (stringValue)")
    let result:JCQRResultViewController = JCQRResultViewController()
    result.urlString = stringValue!
    self.navigationController?.pushViewController(result, animated: true)
}

> 需要注意的是```rectOfInterest``` 这个属性的每一个值取值范围在0~1之间,代表的是对应轴上的比例大小。
![扫一扫.jpeg](http://upload-images.jianshu.io/upload_images/3288439-aa1fbcb29632c52e.jpeg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


识别图片中的二维码
-------------
识别图片中的代码非常简单,用```CIDetector```一句代码就好了。

fileprivate func readQRImage(_ image:UIImage)->String{
//二维码读取
let ciImage:CIImage=CIImage(image:image)!
let context = CIContext(options: nil)
let detector:CIDetector=CIDetector(ofType: CIDetectorTypeQRCode,
context: context, options: [CIDetectorAccuracy:CIDetectorAccuracyHigh])!
let features=detector.features(in: ciImage)
print("扫描到二维码个数:(features.count)")

    var stringValue:String = ""

    if features.count<=0 {
        showAlert("提示", message: "无法解析图片",VC: self)
    }else {
        //遍历所有的二维码,并框出
        for feature in features as! [CIQRCodeFeature] {
            print(feature.messageString)
            stringValue = feature.messageString!
        }
    }
    return stringValue
}

图片 4

5.获取高清的二维码,并展示

因为CIFilter生成的二维码相对而言模糊,达不到设备快速识别的需求,同时用户体验差.所以通过图像绘制的上下文来获得高清的二维码图片.PS:由于获取高清图片不是该章节的重点,相关的代码部分来自网络,放到文章的最后,仅供看考

// 获取二维码self.imageView.image = [self createErWeiMaImageFormCIImage:outputImage withSize:200];
- setOverView { CGFloat width = CGRectGetWidth(self.view.frame); CGFloat height = CGRectGetHeight(self.view.frame); CGFloat x = CGRectGetMinX(_imageView.frame); CGFloat y = CGRectGetMinY(_imageView.frame); CGFloat w = CGRectGetWidth(_imageView.frame); CGFloat h = CGRectGetHeight(_imageView.frame); [self creatView:CGRectMake(0, 0, width, y)]; [self creatView:CGRectMake(0, y, x, h)]; [self creatView:CGRectMake(0, y + h, width, height - y - h)]; [self creatView:CGRectMake(x + w, y, width - x - w, h)];}- creatView:rect { CGFloat alpha = 0.5; UIColor *backColor = [UIColor blueColor]; UIView *view = [[UIView alloc] initWithFrame:rect]; view.backgroundColor = backColor; view.alpha = alpha; [self.view addSubview:view];}

照片选择器选好照片调用的代理方法

- (void)imagePickerController:(UIImagePickerController*)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
    NSString *type = [info objectForKey:UIImagePickerControllerMediaType];
    if ([type isEqualToString:@"public.image"]) {
        UIImage *pickImage = [info objectForKey:UIImagePickerControllerEditedImage];

        NSData *imageData = UIImagePNGRepresentation(pickImage);
        CIImage *ciImage = [CIImage imageWithData:imageData];

        //创建探测器
        CIDetector *detector = [CIDetector detectorOfType:CIDetectorTypeQRCode context:nil options:@{CIDetectorAccuracy: CIDetectorAccuracyLow}];
        NSArray *features = [detector featuresInImage:ciImage];

        NSString *content;
        //取出探测到的数据
//        for (CIQRCodeFeature *result in features) {
//            content = result.messageString;
//        }
        if (features.count > 0) {
            CIQRCodeFeature *feature = features[0];
            content = feature.messageString;
        }

        __weak typeof(self) weakSelf = self;
        //选中图片后先返回扫描页面,然后跳转到新页面进行展示
        [picker dismissViewControllerAnimated:NO completion:^{
            if (!content) {
                //震动
                [weakSelf playBeep];
                [self performSegueWithIdentifier:@"ScanResult" sender:content];
            }else{
                UIAlertController *alertVC = [UIAlertController alertControllerWithTitle:@"扫描提示" message:@"未识别图中的二维码" preferredStyle:UIAlertControllerStyleAlert];
                UIAlertAction *actionCancel = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleCancel handler:nil];
                [alertVC addAction:actionCancel];
                [self presentViewController:alertVC animated:YES completion:nil];
            }
        }];
    }
}

二维码生成描述

主要用到CIFilter类。CIFilter是Core Image中一个比较核心的有关滤镜使用的类。
通常CIFilter对象需要一个或多个图像作为输入,并产生CIImage类型的实体作为输出。而这些输出图像的生产过程需要我们通过设置一些参数来实现,而这些参数的设置和检索都是利用键/值对的形式进行操作的。
其中CIFlilter承载着所有设置好的滤镜参数以CIImage为基础,在CIContext对象中进行渲染。要提一下的是滤镜的使用是可以叠加的,我们可以使用多种滤镜处理同一个图像。Core Image的渲染分为CPU和GPU两种,其中使用CPU渲染可以在后台进行,但是渲染速度没有GPU快,而GPU是不能进行后台渲染的它是实时的,在进行视频帧渲染时我们就可以使用GPU进行渲染。

图片 5

Simulator.png

生成二维码代码

 private func createQRCodeImage(content:String,size:CGSize)->UIImage {
        let stringData = content.data(using: String.Encoding.utf8)
        let qrFilter = CIFilter(name: "CIQRCodeGenerator")

        qrFilter?.setValue(stringData, forKey: "inputMessage")
        qrFilter?.setValue("H", forKey: "inputCorrectionLevel")

        let colorFilter = CIFilter(name: "CIFalseColor")
        colorFilter?.setDefaults()

        colorFilter?.setValuesForKeys(["inputImage" : (qrFilter?.outputImage)!,"inputColor0":CIColor.init(cgColor: UIColor.black.cgColor),"inputColor1":CIColor.init(cgColor: UIColor.white.cgColor)])

        let qrImage = colorFilter?.outputImage
        let cgImage = CIContext(options: nil).createCGImage(qrImage!, from: (qrImage?.extent)!)
        UIGraphicsBeginImageContext(size)
        let context = UIGraphicsGetCurrentContext()
        context!.interpolationQuality = .none
        context!.scaleBy(x: 1.0, y: -1.0)
        context?.draw(cgImage!, in: (context?.boundingBoxOfClipPath)!)
        let codeImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return codeImage!
    }

往往实际应用中不会是单独的二维码,还需要向二维码中添加自己的logo。二维码有很好的纠错机制,我们可以直接向二维码图片中加入logo图片,建议logo面积不要超过二维码有效面积的1/20,否则会影响读码。

private func addIconToQRCodeImage(image:UIImage,icon:UIImage,iconSize:CGSize)->UIImage{
        UIGraphicsBeginImageContext(image.size)

        let imageWidth = image.size.width
        let imageHeight = image.size.height
        let iconWidth = iconSize.width
        let iconHeight = iconSize.height

        image.draw(in: CGRect(x: 0, y: 0, width: imageWidth, height: imageHeight))
        icon.draw(in: CGRect(x: (imageWidth-iconWidth)/2.0, y: (imageHeight-iconHeight)/2.0, width: iconWidth, height: iconHeight))
        let qrImage = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        return qrImage!
    }

[self.view.layer insertSublayer:layer atIndex:0];

关于属性的创建,我们需要了解到每个属性的作用和相关操作

6 初始化扫描配置

四、从相册获取二维码进行扫描

  • iOS8之后,可以使用CIDetector(CIDetector可用于人脸识别)进行图片解析,从而使我们可以便捷的从相册中获取到二维码。
  • 1.调用系统相册,从系统相册中选取图片
    2.使用探测器(CIDetector)对选取的图片进行处理,取得图片二维码中包含的数据信息。
  • 下面是简单的代码实现示例

#import

7).一切准备就去,开始运行
[self.session startRunning];

3. viewDidLoad中进行初始化设置(相关代码中有部分注释,不做过多描述)

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];

    self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"相册" style:UIBarButtonItemStylePlain target:self action:@selector(openPhotos:)];

    CGFloat imageX = ScreenWidth*0.15;
    CGFloat imageY = ScreenWidth*0.15+64;

    // 扫描框中的四个边角的背景图
    UIImageView *scanImage = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"saoyisao"]];
    scanImage.frame = CGRectMake(imageX, imageY, ScreenWidth*0.7, ScreenWidth*0.7);
    [self.view addSubview:scanImage];

    // 上下移动的扫描条
    UIImageView *activeImage = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"saoyisao-3"]];
    activeImage.frame = CGRectMake(imageX, imageY, ScreenWidth*0.7, 4);
    [self.view addSubview:activeImage];
    self.activeImage = activeImage;

    // 扫描框下面的提示按钮
    UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, CGRectGetMaxY(scanImage.frame), ScreenWidth, 40)];
    label.text = @"将二维码放入框内即可自动扫描";
    label.font = [UIFont systemFontOfSize:15];
    label.textAlignment = NSTextAlignmentCenter;
    [self.view addSubview:label];

    //添加全屏的黑色半透明蒙版
    UIView *maskView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
    maskView.backgroundColor = [UIColor colorWithWhite:0.0 alpha:0.5];
    [self.view addSubview:maskView];
    //从蒙版中扣出扫描框那一块,这块的大小尺寸将来也设成扫描输出的作用域大小
    UIBezierPath *maskPath = [UIBezierPath bezierPathWithRect:self.view.bounds];
    [maskPath appendPath:[[UIBezierPath bezierPathWithRect:CGRectMake(imageX, imageY, ScreenWidth*0.7, ScreenWidth*0.7)] bezierPathByReversingPath]];
    CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
    maskLayer.path = maskPath.CGPath;
    maskView.layer.mask = maskLayer;

    // 判断相机权限
    [self checkCaptureStatus];
    // 初始化扫描需要用的相关实例变量
    [self initScanningContent];
    // 开始动画,扫描条上下移动
    [self performSelectorOnMainThread:@selector(timerFired) withObject:nil waitUntilDone:NO];
    // 添加监听->APP从后台返回前台,重新扫描
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sessionStartRunning:) name:UIApplicationDidBecomeActiveNotification object:nil];
}

//获取摄像设备

5.运行展示

下面我们看看运行的结果,这里测试过程包括区域扫描的边缘化测试:

图片 6区域扫描.gif

讲完了二维码的扫描,接下来我们接着讲讲二维码的生成.二维码的生成的核心在于图形的绘制,我们通过滤镜CIFilter和图形绘制的上下文方式生成二维码.

二维码的生成区别于二维码的扫描,因为他的核心是基于图形的绘制完成的,所以需要导入CoreImage框架#import <CoreImage/CoreImage.h>

- (AVCaptureMetadataOutput *)output{ if (_output == nil) { _output = [[AVCaptureMetadataOutput alloc]init]; [_output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()]; //限制扫描区域 [_output setRectOfInterest:[self rectOfInterestByScanViewRect:_imageView.frame]]; } return _output;}

一、如何扫描二维码(相关类介绍)

使用系统提供的AVFoundation框架中提供的扫描API主要会用到以下几个类:

  • AVCaptureSession //输入输出的中间桥梁
  • AVCaptureDevice //获取摄像设备
  • AVCaptureDeviceInput //创建输入流
  • AVCaptureMetadataOutput //创建输出流
  • AVCaptureVideoPreviewLayer //扫描窗口

接下来我们就研究一下这几个类都有什么用。

-(void)stopReading

关于二维码(或者条形码,以下归类简称二维码)扫描和生成的,我相信网络上相关的文章层数不穷,但是,大部分都是直接粘贴上代码,不去解释,这样导致每次遇到诸如此类的功能行的问题,简单方便的CV工程师程序,久而久之,对于程序开发更局限于表面,开发这条道路也会越来越局限了.好了,言归正传,接下来我就分享一下,自己在二维码开发的过程中遇到的问题和一些经验吧.注:这里的扫描仅限于相机扫描,所以建议各位开发者,需要在真机上进行测试

2. 使用AudioServices.h播放音效需要导入AudioToolbox.framework框架

Step2:设置捕获会话

关于这个库的介绍,相信很多做过视频和音频播放的童鞋们并不陌生,这个也是基于cocoa下比较常用的库

if  return;
2. 使用AVCaptureDevice的静态方法获得需要使用的设备,例如拍照和录像就需要获得摄像头设备,录音就要获得麦克风设备。

```

3).初始化输出流Output
self.output = [[AVCaptureMetadataOutput alloc] init];

下面,敲黑板了,这里需要注意的是:在输出流的设置中,如果不对AVCaptureMetadataOutput的属性rectOfInterest进行设置,扫描的区域默认是展示的AVCaptureVideoPreviewLayer全部区域.这里我们采用区域扫描,也就是所谓的条框扫描,提高用户体验度.

// 创建view,通过layer层进行设置边框宽度和颜色,用来辅助展示扫描的区域UIView *redView = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 200, 100)];redView.layer.borderWidth = 2;redView.layer.borderColor = [UIColor cyanColor].CGColor;[self.view addSubview:redView];//设置输出流的相关属性// 确定输出流的代理和所在的线程,这里代理遵循的就是上面我们在准备工作中提到的第一个代理,至于线程的选择,建议选在主线程,这样方便当前页面对数据的捕获.[self.output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
  • 第一步当然是要先打开相册了

4. 设置扫描需要使用的相关变量

/**
 *  添加扫描控件
 */
- (void)initScanningContent{
    //获取摄像设备
    device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
    //创建输入流
    input = [AVCaptureDeviceInput deviceInputWithDevice:device error:nil];

    //创建输出流
    output = [[AVCaptureMetadataOutput alloc]init];
    //设置代理 在主线程里刷新
    [output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];

    //初始化链接对象
    session = [[AVCaptureSession alloc]init];
    //高质量采集率
    [session setSessionPreset:AVCaptureSessionPresetHigh];

    [session addInput:input];
    [session addOutput:output];

    //设置扫码支持的编码格式(如下设置条形码和二维码兼容)
    output.metadataObjectTypes=@[AVMetadataObjectTypeQRCode,AVMetadataObjectTypeEAN13Code, AVMetadataObjectTypeEAN8Code, AVMetadataObjectTypeCode128Code];

    layer = [AVCaptureVideoPreviewLayer layerWithSession:session];
    layer.videoGravity=AVLayerVideoGravityResizeAspectFill;

    //设置相机可视范围--全屏
    layer.frame = self.view.bounds;
    [self.view.layer insertSublayer:layer atIndex:0];

    //开始捕获
    [session startRunning];

    //设置扫描作用域范围(中间透明的扫描框)
    CGRect intertRect = [layer metadataOutputRectOfInterestForRect:CGRectMake(ScreenWidth*0.15, ScreenWidth*0.15+64, ScreenWidth*0.7, ScreenWidth*0.7)];
    output.rectOfInterest = intertRect;
}

}

4.获取输出的二维码
CIImage *outputImage = [filter outputImage];
- (AVCaptureSession *)session{ if (_session == nil) { //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]; } } return _session;}

7. 扫描完成调用代理方法,拿到数据进行处理

/**
 *  获取扫描到的结果
 *
 *  @param captureOutput   输出
 *  @param metadataObjects 结果
 *  @param connection      连接
 */
-(void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection{
    if (metadataObjects.count > 0) {
        [session stopRunning];
        AVMetadataMachineReadableCodeObject *metadataObject = [metadataObjects objectAtIndex:0];
        GCLog(@"stringValue = %@",metadataObject.stringValue);
        if ([[metadataObject type] isEqualToString:AVMetadataObjectTypeQRCode]) {
//            if ([metadataObject.stringValue containsString:@"http://"] || [metadataObject.stringValue containsString:@"https://"]) {
//                NSString *urlResult = metadataObject.stringValue;
//                [[UIApplication sharedApplication] openURL:[NSURL URLWithString:urlResult] options:@{UIApplicationOpenURLOptionUniversalLinksOnly:@NO} completionHandler:nil];
//            }
            [self performSegueWithIdentifier:@"ScanResult" sender:metadataObject.stringValue];
        }
    }
}

if (weakSelf){

6.运行结果

扫描的内容请参考第一节"二维码生成"的运行结果

图片 7二维码生成.gif

我是调皮的分割线

这就是我理解的二维码的生成和二维码的扫描,其中主要的还是针对两个框架的研究,让我学到了很多东西.在学习的过程中,比较建议大家多去查看苹果原生的API,这个对自我理解是比较重要的,网络上的总结出来的,只能作为自己的参考,切不可取而代之,最大的禁忌就是CV工程师的道路,再简单的代码也要自己敲出来.有什么问题欢迎大家留言多多留言,多多交流下面附上demo地址(本人的github上):二维码扫描和生成的Demo地址

NSString *mediaType =AVMediaTypeVideo;AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:mediaType];if(authStatus ==AVAuthorizationStatusRestricted || authStatus ==AVAuthorizationStatusDenied){ UIAlertView *alert =[[UIAlertView alloc] initWithTitle:@“项目名称” message:@"请在iPhone的“设置”-“隐私”-“相机”功能中,找到“项目名称”打开相机访问权限" delegate:nil cancelButtonTitle:@"确定" otherButtonTitles: nil]; [alert show]; return; }

- (UIImage *)getErWeiMaImageFormCIImage:(CIImage *)image withSize: size { CGRect extent = CGRectIntegral(image.extent); CGFloat scale = MIN(size/CGRectGetWidth, size/CGRectGetHeight; // 1.创建bitmap; size_t width = CGRectGetWidth * scale; size_t height = CGRectGetHeight * scale; CGColorSpaceRef cs = CGColorSpaceCreateDeviceGray(); CGContextRef bitmapRef = CGBitmapContextCreate(nil, width, height, 8, 0, cs, (CGBitmapInfo)kCGImageAlphaNone); CIContext *context = [CIContext contextWithOptions:nil]; CGImageRef bitmapImage = [context createCGImage:image fromRect:extent]; CGContextSetInterpolationQuality(bitmapRef, kCGInterpolationNone); CGContextScaleCTM(bitmapRef, scale, scale); CGContextDrawImage(bitmapRef, extent, bitmapImage); // 2.保存bitmap到图片 CGImageRef scaledImage = CGBitmapContextCreateImage(bitmapRef); CGContextRelease(bitmapRef); CGImageRelease(bitmapImage); return [UIImage imageWithCGImage:scaledImage];}
  • ### Zing

[即,创建会话和输出对象]

3.给过滤器CIFilter添加数据

这里需要说明的是,二维码的主要内容可以是如下几种类型(传统的条形码只能放数字):

  • 纯文本
  • URL
  • 名片(这个有待考证,表示我并没有试验过)
// 基于多种类型,我们简单的生成字符串的二维码// 创建字符串NSString *dataString = @"锋绘动漫"; // 将字符串转换成date类型,并通过KVO的形式保存至滤镜CIFilter的inputMessage中NSData *data = [dataString dataUsingEncoding:NSUTF8StringEncoding];[filter setValue:data forKeyPath:@"inputMessage"];
- imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info{ //1 获取选择的图片 UIImage *image = info[UIImagePickerControllerOriginalImage]; //初始化一个监听器 CIDetector *detector = [CIDetector detectorOfType:CIDetectorTypeQRCode context:nil options:@{CIDetectorAccuracy : CIDetectorAccuracyHigh}]; [picker dismissViewControllerAnimated:YES completion:^{ //监测到的结果数组 NSArray *features = [detector featuresInImage:[CIImage imageWithCGImage:image.CGImage]]; if (features.count >= 1) { //结果对象 CIQRCodeFeature *feature = [features objectAtIndex:0]; NSString *scannedResult = feature.messageString; [self showAlertViewWithTitle:@"读取相册二维码" withMessage:scannedResult]; } else { [self showAlertViewWithTitle:@"读取相册二维码" withMessage:@"读取失败"]; } }];}

结束语:新手一枚,胡乱涂鸦,希望批评指教。

1、扫描二维码可以,扫描条形码一直没有反应
2、demo只集成条形码,则没有这个问题

3+). 设置扫描区域的大小(这个也是我在开发中,遇到的最*最^n坑爹的问题,标注一下)

为什么是"3+"呢,主要是本来想将这部分放在后面单独讲,但是考虑到连贯性,就单独做一个补充小节来讲吧,而且属于设置session层的部分.

self.output.rectOfInterest = CGRectMake/(SCREEN_HEIGHT),(SCREEN_WIDTH - 100 - 200)/SCREEN_WIDTH,100/SCREEN_HEIGHT,200/SCREEN_WIDTH);

其实呢,我是想在当前view创建一块CGRectMake(100, 100, 200, 100)的扫描区域,如下图的扫描区域框:

图片 8扫描区域.PNG但是呢,这里需要说明的一点就是,我们如果按照常规的CGRect创建方式去设置,是肯定不对的,他会出现扫描区域不是预设的,为什么呢?原因很蛋疼,因为我们平常的设置CGRect是以左上角为原点,横向增加为+x,纵向增加为+y,横向为宽度width,纵向为高度height,没毛病吧,但是,坑爹的就是output的rectOfInterest是以左上角为原点,x与y数值对调,width和height数值对调,并且,x,y,width和height的数值为0 ~ 1.如下对比图:图片 9常规坐标CGRect坐标计算体系.png图片 10output的rectOfInterest坐标计算体系.png

说明一下:这里的计算对比,针对的是,知道扫描框相对于父视图的位置,我们根据扫描框的CGRect可以计算出需要设置的output的rectOfInterest的CGRect.至于为什么需要对调原理,最近有时间研究研究(苹果这个设计让很多人不解),目前仅供参考.大家要是有相关的方案或者明确为何这样,希望在下面留言,我们一起深究一下.

图片 11对比.png

这里的概念区别于我们所认知的CGRect的设置,建议童鞋们还是手动算一下,之后进行边缘化测试,就是测试二维码从边缘完全进入扫描区域并且存在扫描任务的位置.

 //关闭扫描 [self stopScan]; //键盘下落 [self.view endEditing:YES]; //1 实例化二维码滤镜 CIFilter *filter = [CIFilter filterWithName:@"CIQRCodeGenerator"]; //2 回复滤镜的默认属性(因为滤镜有可能保存上一次的属性) [filter setDefaults]; //3 经字符串转化为NSData NSData *data = [self.textField.text dataUsingEncoding:NSUTF8StringEncoding]; //4 通过KVC设置滤镜,传入data,将来滤镜就知道要通过传入的数据生成二维码 [filter setValue:data forKey:@"inputMessage"]; //5 生成二维码 CIImage *image = [filter outputImage]; //补充:CIImage是CoreImage框架中最基本代表图像的对象,他不仅包含原图像数据,还包含作用在原图像上的滤镜链

二、使用AVFoundation拍照、扫描或视频的一般步骤如下:

//调整扫描区域

4).初始化捕获数据类AVCaptureSession
// 初始化sessionself.session = [[AVCaptureSession alloc]init];// 设置session类型,AVCaptureSessionPresetHigh 是 sessionPreset 的默认值。[_session setSessionPreset:AVCaptureSessionPresetHigh];

补充:这里简单对sessionPreset的属性值进行以下说明:苹果API中提供了如下的四种方式:

// AVCaptureSession 预设适用于高分辨率照片质量的输出AVF_EXPORT NSString *const AVCaptureSessionPresetPhoto NS_AVAILABLE(10_7, 4_0) __TVOS_PROHIBITED;// AVCaptureSession 预设适用于高分辨率照片质量的输出AVF_EXPORT NSString *const AVCaptureSessionPresetHigh NS_AVAILABLE(10_7, 4_0) __TVOS_PROHIBITED;// AVCaptureSession 预设适用于中等质量的输出。 实现的输出适合于在无线网络共享的视频和音频比特率。AVF_EXPORT NSString *const AVCaptureSessionPresetMedium NS_AVAILABLE(10_7, 4_0) __TVOS_PROHIBITED;// AVCaptureSession 预设适用于低质量的输出。为了实现的输出视频和音频比特率适合共享 3G。AVF_EXPORT NSString *const AVCaptureSessionPresetLow NS_AVAILABLE(10_7, 4_0) __TVOS_PROHIBITED;

PS:在API的介绍中,除了以上的迹象,我们还会看到好几种类型,不过不是针对 ipad iphone 的。针对 MAC_OS,不便介绍,感兴趣的可以查看相关API

我的理解:
  • ##### 为了从session中取得数据,我们需要创建一个AVCaptureOutput,AVCaptureOutput代表输出数据,管理着输出到一个movie或者图像,AVCaptureMetadataOutput是AVCaptureOutput的子类。

  • ##### 这里是与捕捉视频流所不一致的地方。我们捕捉视频流需要的是AVCaptureVideoDataOutput,而在这里我们需要捕捉的是二维码信息。因此我们需要AVCaptureMetadataOutput。并且我们需要指定捕捉的metadataObject类型。在这里我们指定的是AVMetadataObjectTypeQRCode,我们还可以指定其他类型,例如PDF417条码类型。。

官方说明:
A capture output for processing timed metadata produced by a capture session.

An AVCaptureMetadataOutput object intercepts metadata objects emitted by its associated capture connection and forwards them to a delegate object for processing. You can use instances of this class to process specific types of metadata included with the input data. You use this class the way you do other output objects, typically by adding it as an output to an [AVCaptureSession]) object.
翻译:
用于处理捕获会话产生的定时元数据的捕获输出。

一个avcapturemetadataoutput对象拦截元数据对象通过其相关的捕获连接发射和转发给处理委托对象。您可以使用这个类的实例处理输入数据所包含的特定类型的元数据。你用这类方式你做其他输出对象,通常通过添加它作为一个输出到avcapturesession对象。

Step1:需要导入:AVFoundation Framework 包含头文件:

#import <AVFoundation/AVFoundation.h>
  • 模糊界面的设置

4. AVCaptureMetadataOutput //创建输出流

//初始化链接对象

4.扫描结果处理

这里就需要用到我们之前设置的两个代理AVCaptureMetadataOutputObjectsDelegate和UIAlertViewDelegate在AVCaptureMetadataOutputObjectsDelegate的代理方法中,有didOutputMetadataObjects这个方法,表示输出的结果,我们扫描二维码的结果将要在这里进行处理- captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection{// 判断扫描结果的数据是否存在if ([metadataObjects count] >0){// 如果存在数据,停止扫描[self.session stopRunning];// AVMetadataMachineReadableCodeObject是AVMetadataObject的具体子类定义的特性检测一维或二维条形码。// AVMetadataMachineReadableCodeObject代表一个单一的照片中发现机器可读的代码。这是一个不可变对象描述条码的特性和载荷。// 在支持的平台上,AVCaptureMetadataOutput输出检测机器可读的代码对象的数组AVMetadataMachineReadableCodeObject * metadataObject = [metadataObjects objectAtIndex:0];// 获取扫描到的信息NSString *stringValue = metadataObject.stringValue;UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"扫描结果"message:stringValuedelegate:selfcancelButtonTitle:nilotherButtonTitles:@"确定", nil];[self.view addSubview:alert];[alert show];}}

在UIAlertViewDelegate代理方法中,我们确认信息后,可以对信息有相应的操作,这里我只是简单的进行了继续进行数据捕捉- alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {[self.session startRunning];}

**5 preview **

1. info.plist 文件权限请求 Privacy - Camera Usage Description等

iOS10对用户的隐私又做了进一步加强,就好像当初iOS8对定位隐私进行加强一样,作为开发者的我们貌似也是应该时刻保持这种对新知识警觉性的。
除了相册的权限,iOS10之后如下的权限请求也是需要我们填写请求描述的,在这里也给大家提醒一下:

  • NSContactsUsageDescription -> 通讯录
  • NSMicrophoneUsageDescription -> 麦克风
  • NSPhotoLibraryUsageDescription -> 相册
  • NSCameraUsageDescription -> 相机
  • NSLocationAlwaysUsageDescription -> 地理位置
  • NSLocationWhenInUseUsageDescription -> 地理位置
  • Privacy - Bluetooth Peripheral Usage Description -> 蓝牙权限
  • Privacy - Speech Recognition Usage Description -> 语音转文字权限
  • Privacy - Calendars Usage Description -> 日历权限
  • Privacy - Contacts Usage Description -> 通讯录权限

}

4).创建AVFoundation中央枢纽捕获类AVCaptureSession。

下面的是关于AVCaptureSession的原生API

To perform a real-time capture, a client may instantiate AVCaptureSession and add appropriate AVCaptureInputs, such as AVCaptureDeviceInput, and outputs, such as AVCaptureMovieFileOutput. [AVCaptureSession startRunning] starts the flow of data from the inputs to the outputs, and [AVCaptureSession stopRunning] stops the flow. A client may set the sessionPreset property to customize the quality level or bitrate of the output.

在苹果的API中大致是这样重点解释的:

  • 执行实时捕获,一个客户可以实例化AVCaptureSession并添加适当AVCaptureInputs,AVCaptureDeviceInput和相关的输出,如AVCaptureMovieFileOutput。
  • [AVCaptureSession startRunning]开始的数据流从输入到输出
  • [AVCaptureSession stopRunning]停止流动。
  • 客户端可以设置sessionPreset属性定制质量水平或输出的比特率@property (strong,nonatomic)AVCaptureSession * session;

本文由必威发布于必威-编程,转载请注明出处:苹果原生二维码的功能确实很强大,则没有这个

TAG标签:
Ctrl+D 将本页面保存为书签,全面了解最新资讯,方便快捷。