图片 2

接纳AVFoundation读取二维码,二维码的扫视和变化高清二维码

在iOS7过后,AVFoundation中就增多了Q路虎极光 Code部分,QQashqai是指Quick Response
。首先,要引进

二维码(Quick Response Code,简称QR
Code)是由水平和垂直三个样子上的线条设计而成的一种二维条形码(barcode)。能够编码网址、电话号码、文本等剧情,能够存款和储蓄大量的数量消息。自iOS
7以来,二维码的改换和读取只需求运用Core
Image框架和AVFoundation框架就能够轻便达成。在这里,大家根本介绍二维码的读取。关于二维码的转移,能够查看运用CIFilter生成二维码文章中的介绍。

前言

前两日作者搭建了类别的光景架构,后天重大完毕的的首页的二维码扫描和二维的改换。废话十分少说,直接奔向宗旨。

第一来张效果图

图片 18F7FD7B6-A03F-4594-8732-4F8DA709E1A3.png

分界面包车型地铁搭建自个儿就不赘述了,作者用storybord搭建,因为懒的去写代码。笔者最首要讲下促成二维码扫描的长河

#import <AVFoundation/AVFoundation.h>

1 二维码的读取

读取二维码也正是经过扫描二维码图像以获得其所含有的数目音讯。须求精通的是,任何条形码(包含二维码)的扫描都以基于录制访问(video
capture),由此供给运用AVFoundation框架。

举目四望二维码的长河即从摄影头捕获二维码图像(input)到解析出字符串内容(output)的进度,首若是经过AVCaptureSession对象来贯彻的。该目的用于协和从输入到输出的数据流,在进行进程中,要求先将输入和出口增加到AVCaptureSession对象中,然后通过发送startRunningstopRunning音讯来运转或终止数据流,最终通过AVCaptureVideoPreviewLayer对象将捕获的录像呈现在显示器上。在此处,输入对象平常是AVCaptureDeviceInput对象,首若是透过AVCaptureDevice的实例来获取,而输出对象常常是AVCaptureMetaDataOutput对象,它是读取二维码的骨干部分,与AVCaptureMetadataOutputObjectsDelegate协议结合使用,能够捕获在输入设备中找到的别的元数据,并将其退换为可读的格式。上边是具体步骤:

  1. 导入AVFoundation框架。

    #import <AVFoundation/AVFoundation.h>
    
  2. 创立三个AVCaptureSession对象。

    AVCaptureSession *captureSession = [[AVCaptureSession alloc] init];
    
  3. 为AVCaptureSession对象增多输入和出口。

    // add input
    NSError *error;
    AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
    AVCaptureDeviceInput *deviceInput = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
    
    [captureSession addInput:deviceInput];
    
    // add output
    AVCaptureMetadataOutput *metadataOutput = [[AVCaptureMetadataOutput alloc] init];
    [captureSession addOutput:metadataOutput];
    
  4. 配置AVCaptureMetaDataOutput对象,首假设安装代理和要管理的元数据对象类型。

    dispatch_queue_t queue = dispatch_queue_create("MyQueue", NULL);
    [metadataOutput setMetadataObjectsDelegate:self queue:queue];
    [metadataOutput setMetadataObjectTypes:@[AVMetadataObjectTypeQRCode]];
    

    亟待注意的是,一定要在出口对象被增多到captureSession之后手艺安装要拍卖的元数据类型,否则会产出上边包车型大巴谬误:

    Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: [AVCaptureMetadataOutput setMetadataObjectTypes:] Unsupported type found - use -availableMetadataObjectTypes'
    
  5. 始建并设置AVCaptureVideoPreviewLayer对象来彰显捕获到的录像。

    AVCaptureVideoPreviewLayer *previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:captureSession];
    [previewLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];
    [previewLayer setFrame:self.view.bounds];
    [self.view.layer addSublayer:previewLayer];
    
  6. 给AVCaptureSession对象发送startRunning音讯以运转录像捕获。

    [captureSession startRunning];
    
  7. 实现AVCaptureMetadataOutputObjectsDelegate的captureOutput:didOutputMetadataObjects:fromConnection:方法来管理捕获到的元数据,并将其读抽出来。

    - (void)captureOutput:(AVCaptureOutput *)output didOutputMetadataObjects:(NSArray<__kindof AVMetadataObject *> *)metadataObjects fromConnection:(AVCaptureConnection *)connection
    {
        if (metadataObjects != nil && metadataObjects.count > 0) {
            AVMetadataMachineReadableCodeObject *metadataObject = metadataObjects.firstObject;
            if ([[metadataObject type] isEqualToString:AVMetadataObjectTypeQRCode]) {
                NSString *message = [metadataObject stringValue];
                [self.label performSelectorOnMainThread:@selector(setText:) withObject:message waitUntilDone:NO];
            }
        }
    }
    

    内需提醒的是,由于AVCaptureMetaDataOutput对象代理的设置,该代理方法会在setMetadataObjectsDelegate:queue:点名的系列上调用,要是急需立异顾客分界面,则必须在主线程中开展。

1.1、扫描线的卡通,通过不停改造扫描线于器皿视图的羁绊,来促成的。
 @IBOutlet weak var scannerLineConstraint: NSLayoutConstraint! // 扫描线的顶部约束

概念一个定时刷新分界面包车型大巴电火花计时器

var timer:CADisplayLink? // 定义一个定时器// 设置冲击波的动画private func startAnimation (){ scannerLineConstraint.constant = -contentViewHeighCons.constant sannerLineImageView.layoutIfNeeded() timer = CADisplayLink(target: self ,selector: "updateScanline") let runloop = NSRunLoop.currentRunLoop() timer?.addToRunLoop(runloop, forMode: NSRunLoopCommonModes) timer?.frameInterval = 3}// 改变约束的方法func updateScanline() { scannerLineConstraint.constant += 10; if scannerLineConstraint.constant == 0 { self.scannerLineConstraint.constant = -contentViewHeighCons.constant }}

创办多个对象

2 应用示范

下边,大家就做贰个之类图所示的二维码阅读器:

里头第一完成的效用有:

  • 由此摄像头实时环顾并读取二维码。
  • 浅析从相册中挑选的二维码图片。

出于二维码的围观是基于实时的录制捕获,因而相关的操作不可能在模拟器上进展测量检验,也无法在尚未相机的配备上开展测量检验。要是想要查看该行使,需求连接本人的HTC设备来运维。

1.2二维码扫描完毕,AVFoundation

首开首入AVFoundation思路:定义二个捕捉会话,然后往会话中增多输入对象,增加输出对象,获取扫描结果。达成:

 // 会话private lazy var captureSession:AVCaptureSession = AVCaptureSession()// 获取输入设备private lazy var deviceInput:AVCaptureDeviceInput? = { //获取摄像头 let device = AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeVideo) do { let deviceInput = try AVCaptureDeviceInput(device: device) return deviceInput }catch { print return nil } }()//创建输出对象private lazy var output:AVCaptureMetadataOutput = AVCaptureMetadataOutput()// 创建预览图层private lazy var previewLayer: AVCaptureVideoPreviewLayer = { let layer = AVCaptureVideoPreviewLayer(session: self.captureSession) layer.frame = UIScreen.mainScreen().bounds return layer}()// 创建聚焦的图层private lazy var focusLayer:CALayer = { let layer = CALayer() layer.frame = UIScreen.mainScreen().bounds return layer }()

创制好了地方的对象后,首先要认清输入对象和输出对象是或不是到场到会话中,假设能的才起来扫描。达成代码如下:

 //MARK: 开启扫描 private func startScaning() { // 如果输入和输出不能添加到会话直接返回 if !captureSession.canAddInput(deviceInput) { return } if !captureSession.canAddOutput { return } //分别加输入和输出添加到会话层 captureSession.addInput(deviceInput) captureSession.addOutput // 设置输出对象的能够解析的类型 output.metadataObjectTypes = output.availableMetadataObjectTypes //设置输出代理 output.setMetadataObjectsDelegate(self, queue: dispatch_get_main_queue // 添加预览图层 view.layer.insertSublayer(previewLayer, atIndex: 0) // 开始扫描 captureSession.startRunning() }

末尾正是扫描成功后,数据在出口代理中拍卖,还会有聚集的贯彻也是在中间落到实处。

extension YJQCRScannerViewController:AVCaptureMetadataOutputObjectsDelegate {internal func captureOutput(captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [AnyObject]!, fromConnection connection: AVCaptureConnection!) { print(metadataObjects) // 1、获取到扫描的数据 let dataString = metadataObjects.last?.stringValue // 2.获取扫描到的二维码的位置 // 2.1转换坐标 for object in metadataObjects { // 2.1.1判断当前获取到的数据, 是否是机器可识别的类型 if object is AVMetadataMachineReadableCodeObject { // 2.1.2将坐标转换界面可识别的坐标 let codeObject = previewLayer.transformedMetadataObjectForMetadataObject(object as! AVMetadataObject) as! AVMetadataMachineReadableCodeObject // 2.1.3绘制图形 drawCorners(codeObject) } } }/** 绘制图形 :param: codeObject 保存了坐标的对象 */private func drawCorners(codeObject: AVMetadataMachineReadableCodeObject){ if codeObject.corners.isEmpty { return } // 1.创建一个图层 let layer = CAShapeLayer() layer.lineWidth = 4 layer.strokeColor = UIColor.redColor().CGColor layer.fillColor = UIColor.clearColor().CGColor // 2.创建路径 let path = UIBezierPath() var point = CGPointZero var index: Int = 0 // 2.1移动到第一个点 // 从corners数组中取出第0个元素, 将这个字典中的x/y赋值给point CGPointMakeWithDictionaryRepresentation((codeObject.corners[index++] as! CFDictionaryRef), &point) path.moveToPoint // 2.2移动到其它的点 while index < codeObject.corners.count { CGPointMakeWithDictionaryRepresentation((codeObject.corners[index++] as! CFDictionaryRef), &point) path.addLineToPoint } // 2.3关闭路径 path.closePath() // 2.4绘制路径 layer.path = path.CGPath // 3.将绘制好的图层添加到drawLayer上 focusLayer.addSublayer}/** 清空边线 */private func clearConers(){ // 1.判断drawLayer上是否有其它图层 if focusLayer.sublayers == nil || focusLayer.sublayers?.count == 0{ return } // 2.移除所有子图层 for subLayer in focusLayer.sublayers! { subLayer.removeFromSuperlayer() }}}

终极就能够兑现扫描二维码的成效了,还是相比简单的。

分界面如下

图片 2DBF06CF3-B316-41E6-A2A3-1C486FE59B4E.png

二维的变型,首要用的是CoreImage中的CIFilter的滤镜功用。完结进度也是相比较轻便的。2.1首先是创办二个滤镜对象

 //1.创建滤镜 let filter = CIFilter(name: "CIQRCodeGenerator")// 设置默认属性 filter?.setDefaults()

2.2 设置须求生成二维的字符串

/设置需要生成二维码的数 filter?.setValue(QRString.dataUsingEncoding(NSUTF8StringEncoding), forKey: "inputMessage")

2.3 获取输出的指标CIImge

 //从滤镜中取出生成好的图片 let ciImage = filter?.outputImage

获取的CIImage的二维码,是不明晰,要求管理一下,让其清晰点。作者定义一个方法

 //将模糊的生成高清的 let bgImage = createNonInterpolatedUIImageFormCIImage(ciImage!, size: 200)

主意具体落到实处如下

/** 根据CIImage生成指定大小的高清UIImage :param: image 指定CIImage :param: size 指定大小 :returns: 生成好的图片 */private func createNonInterpolatedUIImageFormCIImage(image: CIImage, size: CGFloat) -> UIImage { let extent: CGRect = CGRectIntegral(image.extent) let scale: CGFloat = min(size/CGRectGetWidth, size/CGRectGetHeight // 1.创建bitmap; let width = CGRectGetWidth * scale let height = CGRectGetHeight * scale let cs: CGColorSpaceRef = CGColorSpaceCreateDeviceGray()! let bitmapRef = CGBitmapContextCreate(nil, Int, Int, 8, 0, cs, 0)! let context = CIContext(options: nil) let bitmapImage: CGImageRef = context.createCGImage(image, fromRect: extent) CGContextSetInterpolationQuality(bitmapRef, CGInterpolationQuality.None) CGContextScaleCTM(bitmapRef, scale, scale); CGContextDrawImage(bitmapRef, extent, bitmapImage); // 2.保存bitmap到图片 let scaledImage: CGImageRef = CGBitmapContextCreateImage(bitmapRef)! return UIImage(CGImage: scaledImage)}

末段将生成好的二维码,用UIImageView来突显就OK。是还是不是很简短,到那截至了吧,晚安!有如何错误,望指正,笔者接触swift的大运也相当短!!

//捕捉会话@property (nonatomic, strong) AVCaptureSession *captureSession;//展示layer@property (nonatomic, strong) AVCaptureVideoPreviewLayer *videoPreviewLayer;

2.1 制造项目

打开Xcode,创造贰个新的档次(File\New\Project…),选择iOS一栏下的Application中的Single
View Application
模版,然后点击Next,填写项目选项。在Product
Name
中填写QRCodeReaderDemo,选择Objective-C语言,点击Next,选取文件地点,并单击Create创制项目。

回想,在viewDidLoad大校captureSession开端安装为nil。未来上马扫描的主旨部分

2.2 创设分界面

打开Main.storyboard文本,在时下调整器中存放导航调节器,并增多标题QR
Code Reader

在视图调控器中加多ToolBar、Flexible Space Bar Button Item、Bar Button
Item、View,布局如下:

里头,各因素及效果:

  • ToolBar:增加在调控器视图的最尾部,其Bar
    Item标题为Start,具备双重成效,用于运行和休憩扫描。
  • Flexible Space Bar Button
    Item:分别加多在Start的左右两边,用于固定Start
    的职位使其居中显示。
  • Bar Button
    Item:增加在导航栏的右边,题目为Album,用于从相册选用二维码图片进行分析。
  • View:增添在调控器视图的中级,用于稍后设置扫描框。在那边运用机动布局定位宽高均为260,而且水平和垂直方向都以居中。

创制一个名叫ScanView的新文件(File\New\File…),它是UIView的子类。然后选中央广播台图调控器中间增加的View,将该视图的类名改换为ScanView

开发协理编辑器,将storyboard中的元素连接到代码中:

注意,需要在ViewController.m文本中程导弹入ScanView.h文件。

- startReading { NSError *error; //初始化捕捉设备(AVCaptureDevice),类型为AVMediaTypeVideo,该类型可以快速使用设备的摄像头部分 AVCaptureDevice *captureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; //用captureDevice创建输入流 AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:captureDevice error:&error]; if  { NSLog(@"%@", [error localizedDescription]); return NO; } //创建媒体数据输出流 AVCaptureMetadataOutput *captureMetadataOutput = [[AVCaptureMetadataOutput alloc] init]; //实例化捕捉会话 _captureSession = [[AVCaptureSession alloc] init]; //将输入流添加到会话 [_captureSession addInput:input]; //将媒体输出流添加到会话中 [_captureSession addOutput:captureMetadataOutput]; //创建串行队列,并加媒体输出流添加到队列当中 dispatch_queue_t dispatchQueue; dispatchQueue = dispatch_queue_create("myQueue", NULL); //设置代理 [captureMetadataOutput setMetadataObjectsDelegate:self queue:dispatchQueue]; //设置输出媒体数据类型为QRCode [captureMetadataOutput setMetadataObjectTypes:[NSArray arrayWithObject:AVMetadataObjectTypeQRCode]]; //实例化预览图层 _videoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:_captureSession]; //设置预览图层填充方式 [_videoPreviewLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill]; //设置图层的frame [_videoPreviewLayer setFrame:_viewPreview.layer.bounds]; //将图层添加到预览view的图层上 [_viewPreview.layer addSublayer:_videoPreviewLayer]; //设置扫描范围 captureMetadataOutput.rectOfInterest = CGRectMake(0.2f, 0.2f, 0.8f, 0.8f); //扫描框 _boxView = [[UIView alloc] initWithFrame:CGRectMake(_viewPreview.bounds.size.width * 0.2f, _viewPreview.bounds.size.height * 0.2f, _viewPreview.bounds.size.width - _viewPreview.bounds.size.width * 0.4f, _viewPreview.bounds.size.height - _viewPreview.bounds.size.height * 0.4f)]; _boxView.layer.borderColor = [UIColor greenColor].CGColor; _boxView.layer.borderWidth = 1.0f; [_viewPreview addSubview:_boxView]; //扫描线 _scanLayer = [[CALayer alloc] init]; _scanLayer.frame = CGRectMake(0, 0, _boxView.bounds.size.width, 1); _scanLayer.backgroundColor = [UIColor blueColor].CGColor; [_boxView.layer addSublayer:_scanLayer]; NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:0.2f target:self selector:@selector(moveScanLayer:) userInfo:nil repeats:YES]; [timer fire]; //开始扫描 [_captureSession startRunning]; return YES;}}

2.3 增添代码

至于扫描框和扫描线,这几个实际上是可以去掉的,设置扫描范围后只要二维码步向到范围内就足以博获得相应的音讯。所以这一块能够依附自身的喜好开展设置,扫描线只要设置二个电火花计时器然后让线不断上下移动就行了。

2.3.1 扫描二维码

首先在ViewController.h文本中程导弹入AVFoundation框架:

#import <AVFoundation/AVFoundation.h>

切换到ViewController.m文件,添加AVCaptureMetadataOutputObjectsDelegate探究,并在接口部分增多上面包车型客车质量:

@interface ViewController ()<AVCaptureMetadataOutputObjectsDelegate>

// properties
@property (assign, nonatomic) BOOL isReading;
@property (strong, nonatomic) AVCaptureSession *captureSession;
@property (strong, nonatomic) AVCaptureVideoPreviewLayer *previewLayer;

viewDidLoad主意中增加上面代码:

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.isReading = NO;
    self.captureSession = nil;
}

接下来在促成都部队分加多startScanning方法和stopScanning办法及有关代码:

- (void)startScanning
{
    self.captureSession = [[AVCaptureSession alloc] init];

    // add input
    NSError *error;
    AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
    AVCaptureDeviceInput *deviceInput = [[AVCaptureDeviceInput alloc] initWithDevice:device error:&error];
    if (!deviceInput) {
        NSLog(@"%@", [error localizedDescription]);
    }
    [self.captureSession addInput:deviceInput];

    // add output
    AVCaptureMetadataOutput *metadataOutput = [[AVCaptureMetadataOutput alloc] init];
    [self.captureSession addOutput:metadataOutput];

    // configure output
    dispatch_queue_t queue = dispatch_queue_create("MyQueue", NULL);
    [metadataOutput setMetadataObjectsDelegate:self queue:queue];
    [metadataOutput setMetadataObjectTypes:@[AVMetadataObjectTypeQRCode]];

    // configure previewLayer
    self.previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.captureSession];
    [self.previewLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];
    [self.previewLayer setFrame:self.view.bounds];
    [self.view.layer addSublayer:self.previewLayer];

    // start scanning
    [self.captureSession startRunning];
}

- (void)stopScanning
{
    [self.captureSession stopRunning];
    self.captureSession = nil;

    [self.previewLayer removeFromSuperlayer];
}

找到startStopAction:并在该格局中调用上边包车型地铁主意:

- (IBAction)startStopAction:(id)sender
{
    if (!self.isReading) {
        [self startScanning];
        [self.view bringSubviewToFront:self.toolBar];
        [self.startStopButton setTitle:@"Stop"];
    }
    else {
        [self stopScanning];
        [self.startStopButton setTitle:@"Start"];
    }

    self.isReading = !self.isReading;
}

由来,二维码扫描相关的代码已经完毕,假如想要它亦可健康运作以来,还亟需在Info.plist文件中增添NSCameraUsageDescription键及相应描述以访谈相机:

供给专一的是,以后只得扫描二维码可是还不能够读取到二维码中的内容,可是大家得以连接装置,运营试下:

悬停扫描

2.3.2 读取二维码

读取二维码必要贯彻AVCaptureMetadataOutputObjectsDelegate公约的captureOutput:didOutputMetadataObjects:fromConnection:方法:

- (void)captureOutput:(AVCaptureOutput *)output didOutputMetadataObjects:(NSArray<__kindof AVMetadataObject *> *)metadataObjects fromConnection:(AVCaptureConnection *)connection
{
    if (metadataObjects != nil && metadataObjects.count > 0) {
        AVMetadataMachineReadableCodeObject *metadataObject = metadataObjects.firstObject;
        if ([[metadataObject type] isEqualToString:AVMetadataObjectTypeQRCode]) {
            NSString *message = [metadataObject stringValue];
            [self performSelectorOnMainThread:@selector(displayMessage:) withObject:message waitUntilDone:NO];

            [self performSelectorOnMainThread:@selector(stopScanning) withObject:nil waitUntilDone:NO];
            [self.startStopButton performSelectorOnMainThread:@selector(setTitle:) withObject:@"Start" waitUntilDone:NO];
            self.isReading = NO;
        }
    }
}

- (void)displayMessage:(NSString *)message
{
    UIViewController *vc = [[UIViewController alloc] init];

    UITextView *textView = [[UITextView alloc] initWithFrame:vc.view.bounds];
    [textView setText:message];
    [textView setFont:[UIFont preferredFontForTextStyle:UIFontTextStyleBody]];
    textView.editable = NO;

    [vc.view addSubview:textView];

    [self.navigationController showViewController:vc sender:nil];
}

在此地大家将扫码结果展现在二个新的视图中,如若你运转程序的话应该能够看看扫描的二维码内容了。

其余,为了使大家的运用更逼真,能够在围观到二维码消息时让它播放音响。那第一供给在类型中增多三个音频文件:

下一场在接口部分增多贰个AV奥迪oPlayer对象的性质:

@property (strong, nonatomic) AVAudioPlayer *audioPlayer;

在落实部分增添loadSound办法及代码,并在viewDidLoad中调用该措施:

- (void)loadSound
{
    NSString *soundFilePath = [[NSBundle mainBundle] pathForResource:@"beep" ofType:@"mp3"];
    NSURL *soundURL = [NSURL URLWithString:soundFilePath];
    NSError *error;

    self.audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:soundURL error:&error];

    if (error) {
        NSLog(@"Could not play sound file.");
        NSLog(@"%@", [error localizedDescription]);
    }
    else {
        [self.audioPlayer prepareToPlay];
    }
}

- (void)viewDidLoad
{
    ...    
    [self loadSound];
}

最后,在captureOutput:didOutputMetadataObjects:fromConnection:格局中增添上边包车型客车代码来播音声音:

- (void)captureOutput:(AVCaptureOutput *)output didOutputMetadataObjects:(NSArray<__kindof AVMetadataObject *> *)metadataObjects fromConnection:(AVCaptureConnection *)connection
{
    if (metadataObjects != nil && metadataObjects.count > 0) {
        AVMetadataMachineReadableCodeObject *metadataObject = metadataObjects.firstObject;
        if ([[metadataObject type] isEqualToString:AVMetadataObjectTypeQRCode]) {
            ...
            self.isReading = NO;

            // play sound
            if (self.audioPlayer) {
                [self.audioPlayer play];
            }
        }
    }
 [_captureSession stopRunning]; _captureSession = nil; [_scanLayer removeFromSuperlayer]; [_videoPreviewLayer removeFromSuperlayer];
2.3.3 设置扫描框

当下点击Start开关,整个视图范围都足以扫描二维码。今后,大家必要设置一个扫描框,以限制独有扫描框区域内的二维码被读取。在此间,将围观区域设置为storyboard中加多的视图,即scanView。

在落到实处部分找到startReading艺术,加多下面包车型地铁代码:

- (void)startScanning
{
    // configure previewLayer
    ...

    // set the scanning area
    [[NSNotificationCenter defaultCenter] addObserverForName:AVCaptureInputPortFormatDescriptionDidChangeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
        metadataOutput.rectOfInterest = [self.previewLayer metadataOutputRectOfInterestForRect:self.scanView.frame];
    }];

    // start scanning
    ...
}

亟需专一的是,rectOfInterest特性无法在安装 metadataOutput
时一贯设置,而需求在AVCaptureInputPortFormatDescriptionDidChangeNotification文告里安装,不然
metadataOutputRectOfInterestForRect:方法会再次回到 (0, 0, 0, 0)。

为了让扫描框更实际的来得,大家要求自定义ScanView,为其绘制边框、四角以及扫描线。

率先展开ScanView.m文本,在促成都部队分重写initWithCoder:主意,为scanView设置透明的背景颜色:

- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
    self = [super initWithCoder:aDecoder];

    if (self) {
        self.backgroundColor = [UIColor clearColor];
    }

    return self;
}

接下来重写drawRect:方法,为scanView绘制边框和四角:

- (void)drawRect:(CGRect)rect
{
    CGContextRef context = UIGraphicsGetCurrentContext();

    // 绘制白色边框
    CGContextAddRect(context, self.bounds);
    CGContextSetStrokeColorWithColor(context, [UIColor whiteColor].CGColor);
    CGContextSetLineWidth(context, 2.0);
    CGContextStrokePath(context);

    // 绘制四角:
    CGContextSetStrokeColorWithColor(context, [UIColor greenColor].CGColor);
    CGContextSetLineWidth(context, 5.0);

    // 左上角:
    CGContextMoveToPoint(context, 0, 30);
    CGContextAddLineToPoint(context, 0, 0);
    CGContextAddLineToPoint(context, 30, 0);
    CGContextStrokePath(context);

    // 右上角:
    CGContextMoveToPoint(context, self.bounds.size.width - 30, 0);
    CGContextAddLineToPoint(context, self.bounds.size.width, 0);
    CGContextAddLineToPoint(context, self.bounds.size.width, 30);
    CGContextStrokePath(context);

    // 右下角:
    CGContextMoveToPoint(context, self.bounds.size.width, self.bounds.size.height - 30);
    CGContextAddLineToPoint(context, self.bounds.size.width, self.bounds.size.height);
    CGContextAddLineToPoint(context, self.bounds.size.width - 30, self.bounds.size.height);
    CGContextStrokePath(context);

    // 左下角:
    CGContextMoveToPoint(context, 30, self.bounds.size.height);
    CGContextAddLineToPoint(context, 0, self.bounds.size.height);
    CGContextAddLineToPoint(context, 0, self.bounds.size.height - 30);
    CGContextStrokePath(context);    
}

设若指望在扫描进程中看出刚才绘制的扫描框,还索要切换来ViewController.m文件,在startStopAction:方法中增加下边包车型大巴代码来展示扫描框:

- (IBAction)startStopAction:(id)sender
{
    if (!self.isReading) {
        ...
        [self.view bringSubviewToFront:self.toolBar];   // display toolBar
        [self.view bringSubviewToFront:self.scanView];  // display scanView
        ...
    }
    ...
}

明天运作,你拜候到上面的功效:

接下去我们承接加多扫描线。

首先在ScanView.h文件的接口部分声雅培个NSTimer对象的性质:

@property (nonatomic, strong) NSTimer *timer;

然后切换来ScanView.m文本,在贯彻部分增加loadScanLine措施及代码,并在initWithCoder:方式中调用:

- (void)loadScanLine
{
    self.timer = [NSTimer scheduledTimerWithTimeInterval:3.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
        UIView *lineView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.bounds.size.width, 1.0)];
        lineView.backgroundColor = [UIColor greenColor];
        [self addSubview:lineView];

        [UIView animateWithDuration:3.0 animations:^{
            lineView.frame = CGRectMake(0, self.bounds.size.height, self.bounds.size.width, 2.0);
        } completion:^(BOOL finished) {
            [lineView removeFromSuperview];
        }];
    }];
}

- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
    ...

    if (self) {
        ...
        [self loadScanLine];
    }

    ...
}

下一场切换成ViewController.m文件,在startStopAction:主意中增加上边代码以启用和行车制动器踏板反应计时器:

- (IBAction)startStopAction:(id)sender
{
    if (!self.isReading) {
        ...
        [self.view bringSubviewToFront:self.scanView];  // display scanView
        self.scanView.timer.fireDate = [NSDate distantPast];    //start timer
        ...
    }
    else {
        [self stopScanning];
        self.scanView.timer.fireDate = [NSDate distantFuture];  //stop timer
        ...
    }

    ...
}

最后,再在viewWillAppear:的重写方法中增多上面代码:

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];

    self.scanView.timer.fireDate = [NSDate distantFuture];
}

能够运营看下:

扫描二维码后踏入相应的主意

2.3.4 从图片剖判二维码

从iOS 8早先,能够行使Core
Image框架中的CIDetector分析图片中的二维码。在那么些利用中,大家透过点击Album开关,从相册选取二维码来剖析。

在写代码以前,供给在Info.plist文本中增加NSPhotoLibraryAddUsageDescription键及相应描述以访谈相册:

然后在ViewController.m文本中增添UIImagePickerControllerDelegateUINavigationControllerDelegate协议:

@interface ViewController ()<AVCaptureMetadataOutputObjectsDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate>

在促成都部队分找到readingFromAlbum:办法,加多底下代码以访谈相册中的图片:

- (IBAction)readingFromAlbum:(id)sender
{
    UIImagePickerController *picker = [[UIImagePickerController alloc] init];
    picker.delegate = self;
    picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
    picker.allowsEditing = YES;

    [self presentViewController:picker animated:YES completion:nil];
}

接下来实现UIImagePickerControllerDelegate的imagePickerController:didFinishPickingMediaWithInfo:方法以深入分析选用的二维码图片:

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info
{
    [picker dismissViewControllerAnimated:YES completion:nil];

    UIImage *selectedImage = [info objectForKey:UIImagePickerControllerEditedImage];
    CIImage *ciImage = [[CIImage alloc] initWithImage:selectedImage];

    CIDetector *detector = [CIDetector detectorOfType:CIDetectorTypeQRCode context:nil options:@{CIDetectorAccuracy:CIDetectorAccuracyLow}];
    NSArray *features = [detector featuresInImage:ciImage];

    if (features.count > 0) {
        CIQRCodeFeature *feature = features.firstObject;
        NSString *message = feature.messageString;

        // display message
        [self displayMessage:message];

        // play sound
        if (self.audioPlayer) {
            [self.audioPlayer play];
        }
    }
}

明日得以运作试下从相册选用二维码来读取:

上海教室彰显的是在模拟器中运营的结果。

至此,大家的二维码阅读器已经整整成功,要是须求完整代码,能够下载QRCodeReaderDemo查看。

- captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection{ //判断是否有数据 if (metadataObjects != nil && [metadataObjects count] > 0) { AVMetadataMachineReadableCodeObject *metadataObj = [metadataObjects objectAtIndex:0]; //判断回传的数据类型 if ([[metadataObj type] isEqualToString:AVMetadataObjectTypeQRCode]) { //对获取信息进行处理 } }}

3 仿照效法资料

Still and Video Media
Capture

Building a Barcode and QR Code Reader in Swift 4 and Xcode
9

How To Scan QR Code Using AVFoundation
Framework

Barcode scanning in iOS using
AVFoundation

HOW TO SAVE/LOAD IMAGE/VIDEOS FROM CAMERA ROLL – XCODE
IOS

iOS 原生扫 Q智跑码的那多少个事

那般就落到实处了二维码扫描的功效

再不怕有关二维码的扭转

先是创设二个UIImageVIew对象

@property (weak, nonatomic) IBOutlet UIImageView *image;

生成二维码,笔者这里是私下认可将字符串设置了生成二维码

//生成二维码- (UIImage *)generateQRCode:(NSString *)str width:width height:height { // 生成二维码图片 CIImage *qrcodeImage; NSData *data = [str dataUsingEncoding:NSISOLatin1StringEncoding allowLossyConversion:false]; CIFilter *filter = [CIFilter filterWithName:@"CIQRCodeGenerator"]; [filter setValue:data forKey:@"inputMessage"]; [filter setValue:@"H" forKey:@"inputCorrectionLevel"]; qrcodeImage = [filter outputImage]; // 消除模糊 CGFloat scaleX = width / qrcodeImage.extent.size.width; // extent 返回图片的frame CGFloat scaleY = height / qrcodeImage.extent.size.height; CIImage *transformedImage = [qrcodeImage imageByApplyingTransform:CGAffineTransformScale(CGAffineTransformIdentity, scaleX, scaleY)]; return [UIImage imageWithCIImage:transformedImage];}

末段将回来的图样体现到对应的岗位就能够了