iOS開發(fā) - 二維碼的掃描

一、前言

最近在做一個(gè)關(guān)于掃描二維碼簽到的小東西,所以還是上來寫一篇關(guān)于二維碼的文章,網(wǎng)上也有一些掃描二維碼的框架,例如ZXing或者ZBar。但是感覺還不如用原生的好,所以果斷采用原生的了。本文介紹的二維碼的掃描,就是顯示二維碼掃描的結(jié)果,至于鏈接的跳轉(zhuǎn)和應(yīng)用的打開,就不多說明,只要在plist文件和掃描的代理方法里面做處理就好了。
ps:

  • 二維碼的掃描要調(diào)用相機(jī),模擬器是不支持相機(jī)的,所以用模擬器測(cè)試的話,是會(huì)崩潰。
  • 原生的二維碼掃描不支持圖像識(shí)別,只支持?jǐn)z像頭掃描識(shí)別。

二、相關(guān)類的介紹

  1. AVCaptureDevice:代表抽象的硬件設(shè)備。
  2. AVCaptureDeviceInput:輸入設(shè)備
  3. AVCaptureMetadataOutput:輸出類,掃描的碼的類型均由這個(gè)類管理。
  4. AVCaptureSession:會(huì)話對(duì)象,連接輸入設(shè)備和輸出設(shè)備。
  5. AVCaptureVideoPreviewLayer:圖層類,將相機(jī)掃描到的圖像實(shí)時(shí)顯示在屏幕上。

三、掃描的界面的搭建

  • 界面效果預(yù)覽
Snip20160807_2.png
  • 在屏幕中央,拖了一個(gè)view,作為掃描的區(qū)域框,并設(shè)置好它的約束。
  • 如果你手上的圖片是下面這種的話,就可以跟我一樣,添加4個(gè)imageView到掃描的區(qū)域框內(nèi),然后分別設(shè)置好它們的約束,讓它們分別在掃描區(qū)域框的四個(gè)角上。
Snip20160808_1.png
  • 如果你手上是下面的這種圖片,則需要處理一下,具體步驟如下圖所示,就是對(duì)其進(jìn)行一定的拉伸處理。這樣,在屏幕中央,不是拖出一個(gè)view,而是拖出一個(gè)imageView,設(shè)置它的image為你的圖片。
Snip20160807_4.png
Snip20160807_6.png
Snip20160808_2.png
  • 最后就是掃描區(qū)域那根線的添加了,這里我是在代碼里面進(jìn)行添加,并設(shè)置了相關(guān)的動(dòng)畫,然后在- (void)viewWillAppear:(BOOL)animated方法里面進(jìn)行調(diào)用。具體代碼如下:
/**
 *  添加掃描線以及開啟掃描線的動(dòng)畫
 */
-(void)startAnimate {
    CGFloat scanImageViewX = self.scanView.frame.origin.x;
    CGFloat scanImageViewY = self.scanView.frame.origin.y;
    CGFloat scanImageViewW = self.scanViewWidth.constant;
    CGFloat scanImageViewH = 7;
    
    _scanImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"scanLine"]];
    _scanImageView.frame = CGRectMake(scanImageViewX, scanImageViewY, scanImageViewW, scanImageViewH);
    [self.scanView addSubview:_scanImageView];
    
    [UIView animateWithDuration:2.0 delay:0 options:UIViewAnimationOptionRepeat animations:^{
        _scanImageView.frame = CGRectMake(scanImageViewX, scanImageViewY + self.scanViewHeight.constant, scanImageViewW, scanImageViewH);
    } completion:nil];
}

四、具體代碼

  • 設(shè)備,輸入源和輸出源的懶加載
/**
 *  懶加載設(shè)備
 */
-(AVCaptureDevice *)device {
    if (!_device) {
        _device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
    }
    return _device;
}
/**
 *  懶加輸入源
 */
-(AVCaptureDeviceInput *)input {
    if (!_input) {
        _input = [AVCaptureDeviceInput deviceInputWithDevice:self.device error:nil];
    }
    return _input;
}
/**
 *  懶加載輸出源
 */
-(AVCaptureMetadataOutput *)output {
    if (!_output) {
        _output = [[AVCaptureMetadataOutput alloc] init];
        [_output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
        
    }
    return _output;
}
  • 設(shè)置掃描二維碼的方法,在- (void)viewDidLoad方法里進(jìn)行調(diào)用。
    1、該方法里面,創(chuàng)建會(huì)話設(shè)備,并設(shè)置為高質(zhì)量的采集,然后分別判斷添加輸入源和輸入源到會(huì)話中。
    2、條碼的類型,我這里直接把全部碼所在的數(shù)組都放進(jìn)去了,比較方便吧,當(dāng)然只設(shè)置其中幾種條碼也可以。
    3、設(shè)置掃描的范圍,我們下面再說。
    4、創(chuàng)建一個(gè)預(yù)覽的圖層,將會(huì)話作為創(chuàng)建的參數(shù)傳入,并圖層為鋪滿整個(gè)屏幕。
    5、創(chuàng)建一個(gè)非掃描區(qū)域的黑色蒙板圖層,設(shè)置它的代理為當(dāng)前的控制器,并實(shí)現(xiàn)它的代理方法,它的代理方法其實(shí)就是創(chuàng)建一個(gè)蒙板,代理方法具體的實(shí)現(xiàn),待會(huì)在下面會(huì)貼出代碼。
/**
 *  設(shè)置掃描二維碼
 */
-(void)setupScanQRCode {
    // 1、創(chuàng)建設(shè)備會(huì)話對(duì)象,用來設(shè)置設(shè)備數(shù)據(jù)輸入
    _session = [[AVCaptureSession alloc] init];
    [_session setSessionPreset: AVCaptureSessionPresetHigh];    //高質(zhì)量采集
    
    if ([_session canAddInput:self.input]) {
        [_session addInput:self.input];
    }
    if ([_session canAddOutput:self.output]) {
        [_session addOutput:self.output];
    }
    // 2.設(shè)置條碼類型為二維碼
    [self.output setMetadataObjectTypes:self.output.availableMetadataObjectTypes];
    
    // 3.設(shè)置掃描范圍
    [self setOutputInterest];
    
    // 4、實(shí)時(shí)獲取攝像頭原始數(shù)據(jù)顯示在屏幕上
    _preview = [AVCaptureVideoPreviewLayer layerWithSession:_session];
    _preview.videoGravity = AVLayerVideoGravityResizeAspectFill;
    _preview.frame = self.view.layer.bounds;
    self.view.layer.backgroundColor = [[UIColor blackColor] CGColor];
    [self.view.layer insertSublayer:_preview atIndex:0];
    
    // 5.設(shè)置非掃描區(qū)域的黑色蒙版圖層
    self.maskLayer = [[CALayer alloc]init];
    self.maskLayer.frame = self.view.layer.bounds;
    self.maskLayer.delegate = self;
    [self.view.layer insertSublayer:self.maskLayer above:_preview];
    [self.maskLayer setNeedsDisplay];
}
  • 設(shè)置掃描范圍
    關(guān)于掃描范圍,這是一個(gè)坑,稍稍不注意,就會(huì)踩進(jìn)去了。掃描的范圍是通過這個(gè)參數(shù)rectOfInterest來設(shè)置的,但這個(gè)參數(shù)不是普通的CGRect,而是0~1的一個(gè)范圍比例。正確的創(chuàng)建為CGRectMake(y/Height,x/Width,height/Height,width/Width),這里左邊是掃描區(qū)域的x,y,width,height,右邊的是當(dāng)前控制器view的Width和Height。具體的代碼實(shí)現(xiàn)如下:
/**
 *  設(shè)置二維碼的掃描范圍
 */
-(void)setOutputInterest {
    CGSize size = self.view.bounds.size;
    CGFloat scanViewWidth = 240;
    CGFloat scanViewHeight = 240;
    CGFloat scanViewX = (size.width - scanViewWidth) / 2;
    CGFloat scanViewY = (size.height - scanViewHeight) / 2;
    CGFloat p1 = size.height/size.width;
    CGFloat p2 = 1920./1080.;
    if (p1 < p2) {
        CGFloat fixHeight = self.view.bounds.size.width * 1920. / 1080.;
        CGFloat fixPadding = (fixHeight - size.height)/2;
        _output.rectOfInterest = CGRectMake((scanViewY + fixPadding) / fixHeight,
                                            scanViewX / size.width,
                                            scanViewHeight / fixHeight,
                                            scanViewWidth / size.width);
    } else {
        CGFloat fixWidth = self.view.bounds.size.height * 1080. / 1920.;
        CGFloat fixPadding = (fixWidth - size.width)/2;
        _output.rectOfInterest = CGRectMake(scanViewY / size.height,
                                            (scanViewX + fixPadding) / fixWidth,
                                            scanViewHeight / size.height,
                                            scanViewWidth / fixWidth);
    }
}
  • 蒙板的代理方法如下:
/**
 *   蒙板生成,需設(shè)置代理,并在退出頁面時(shí)取消代理
 */
-(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx{
    if (layer == self.maskLayer) {
        UIGraphicsBeginImageContextWithOptions(self.maskLayer.frame.size, NO, 1.0);
        CGContextSetFillColorWithColor(ctx, [UIColor colorWithRed:0 green:0 blue:0 alpha:0.6].CGColor);
        CGContextFillRect(ctx, self.maskLayer.frame);
        CGRect scanFrame = [self.view convertRect:self.scanView.frame fromView:self.scanView.superview];
        CGContextClearRect(ctx, scanFrame);
    }
}
  • 二維碼掃描的回調(diào)方法的具體實(shí)現(xiàn)如下:這里我用了一個(gè)遮蓋的框架SVProgressHUD,模擬一下掃描的等待過程,瞎裝一下。
-void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection {
    NSString *stringValue;
    
    // 顯示遮蓋
    [SVProgressHUD show];
    
    if ([metadataObjects count ] > 0 ) {
        // 當(dāng)掃描到數(shù)據(jù)時(shí),停止掃描
        [ _session stopRunning ];
        
        // 將掃描的線從父控件中移除
        [_scanImageView removeFromSuperview];
        
        AVMetadataMachineReadableCodeObject * metadataObject = [metadataObjects objectAtIndex : 0 ];
        
        stringValue = metadataObject. stringValue ;
    }
    // 當(dāng)前延遲1.0秒
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        // 隱藏遮蓋
        [SVProgressHUD dismiss];
        
        // 將掃描后的結(jié)果顯示在label上
        self.scanResult.text = stringValue;
    });
}
  • 這里,附帶一下方法的代用和開始掃描方法的調(diào)用
-(void)viewDidLoad {
    [super viewDidLoad];
    
    // 設(shè)置掃描二維碼
    [self setupScanQRCode];
}
-(void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    
    // 添加掃描線以及開啟掃描線的動(dòng)畫
    [self startAnimate];
    
    // 開啟二維碼掃描
    [_session startRunning];
}
-(void)dealloc{
    // 刪除預(yù)覽圖層
    if (_preview) {
        [_preview removeFromSuperlayer];
    }
    if (self.maskLayer) {
        self.maskLayer.delegate = nil;
    }
}

五、以上就是所有的代碼了,那么看一下運(yùn)行的結(jié)果吧,這里我只掃描了2二維碼和條形碼。

ps:截gif的時(shí)候,出了點(diǎn)差錯(cuò),勿怪啊。


scanRQCode.gif
scanRQCode1.gif

六、總結(jié)

蘋果原生的二維碼,iOS 7開始有了,到現(xiàn)在網(wǎng)上已經(jīng)有很多大牛寫了技術(shù)博客和文章,要學(xué)習(xí)起來還是非常簡(jiǎn)單的。在我看來,就設(shè)置掃描范圍那里有點(diǎn)坑,需要注意。最后,附上demo吧,因?yàn)槭荴code 8創(chuàng)建的項(xiàng)目,Xcode 7的小伙伴就慎重下載吧,因?yàn)閄code 7是沒辦法打開Xcode 8創(chuàng)建的xib文件的。

特別說明:

iOS 10 的權(quán)限問題,因?yàn)閕OS 10 對(duì)權(quán)限的要求更高了,如果我們不設(shè)置相應(yīng)權(quán)限問題的話,會(huì)直接導(dǎo)致程序崩潰。這時(shí),我們需要在plist文件中加上相機(jī)權(quán)限的描述

<key>NSCameraUsageDescription</key>
 <string>cameraDesciption</string>

本文的demo:二維碼的掃描

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容