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

一、前言

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

  • 二維碼的掃描要調(diào)用相機,模擬器是不支持相機的,所以用模擬器測試的話,是會崩潰。
  • 原生的二維碼掃描不支持圖像識別,只支持攝像頭掃描識別。

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

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

三、掃描的界面的搭建

  • 界面效果預覽
Snip20160807_2.png
  • 在屏幕中央,拖了一個view,作為掃描的區(qū)域框,并設(shè)置好它的約束。
  • 如果你手上的圖片是下面這種的話,就可以跟我一樣,添加4個imageView到掃描的區(qū)域框內(nèi),然后分別設(shè)置好它們的約束,讓它們分別在掃描區(qū)域框的四個角上。
Snip20160808_1.png
  • 如果你手上是下面的這種圖片,則需要處理一下,具體步驟如下圖所示,就是對其進行一定的拉伸處理。這樣,在屏幕中央,不是拖出一個view,而是拖出一個imageView,設(shè)置它的image為你的圖片。
Snip20160807_4.png
Snip20160807_6.png
Snip20160808_2.png
  • 最后就是掃描區(qū)域那根線的添加了,這里我是在代碼里面進行添加,并設(shè)置了相關(guān)的動畫,然后在- (void)viewWillAppear:(BOOL)animated方法里面進行調(diào)用。具體代碼如下:
/**
 *  添加掃描線以及開啟掃描線的動畫
 */
-(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方法里進行調(diào)用。
    1、該方法里面,創(chuàng)建會話設(shè)備,并設(shè)置為高質(zhì)量的采集,然后分別判斷添加輸入源和輸入源到會話中。
    2、條碼的類型,我這里直接把全部碼所在的數(shù)組都放進去了,比較方便吧,當然只設(shè)置其中幾種條碼也可以。
    3、設(shè)置掃描的范圍,我們下面再說。
    4、創(chuàng)建一個預覽的圖層,將會話作為創(chuàng)建的參數(shù)傳入,并圖層為鋪滿整個屏幕。
    5、創(chuàng)建一個非掃描區(qū)域的黑色蒙板圖層,設(shè)置它的代理為當前的控制器,并實現(xiàn)它的代理方法,它的代理方法其實就是創(chuàng)建一個蒙板,代理方法具體的實現(xiàn),待會在下面會貼出代碼。
/**
 *  設(shè)置掃描二維碼
 */
-(void)setupScanQRCode {
    // 1、創(chuàng)建設(shè)備會話對象,用來設(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ù)據(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)于掃描范圍,這是一個坑,稍稍不注意,就會踩進去了。掃描的范圍是通過這個參數(shù)rectOfInterest來設(shè)置的,但這個參數(shù)不是普通的CGRect,而是0~1的一個范圍比例。正確的創(chuàng)建為CGRectMake(y/Height,x/Width,height/Height,width/Width),這里左邊是掃描區(qū)域的x,y,width,height,右邊的是當前控制器view的Width和Height。具體的代碼實現(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è)置代理,并在退出頁面時取消代理
 */
-(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)方法的具體實現(xiàn)如下:這里我用了一個遮蓋的框架SVProgressHUD,模擬一下掃描的等待過程,瞎裝一下。
-void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection {
    NSString *stringValue;
    
    // 顯示遮蓋
    [SVProgressHUD show];
    
    if ([metadataObjects count ] > 0 ) {
        // 當掃描到數(shù)據(jù)時,停止掃描
        [ _session stopRunning ];
        
        // 將掃描的線從父控件中移除
        [_scanImageView removeFromSuperview];
        
        AVMetadataMachineReadableCodeObject * metadataObject = [metadataObjects objectAtIndex : 0 ];
        
        stringValue = metadataObject. stringValue ;
    }
    // 當前延遲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];
    
    // 添加掃描線以及開啟掃描線的動畫
    [self startAnimate];
    
    // 開啟二維碼掃描
    [_session startRunning];
}
-(void)dealloc{
    // 刪除預覽圖層
    if (_preview) {
        [_preview removeFromSuperlayer];
    }
    if (self.maskLayer) {
        self.maskLayer.delegate = nil;
    }
}

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

ps:截gif的時候,出了點差錯,勿怪啊。


scanRQCode.gif
scanRQCode1.gif

六、總結(jié)

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

特別說明:

iOS 10 的權(quán)限問題,因為iOS 10 對權(quán)限的要求更高了,如果我們不設(shè)置相應權(quán)限問題的話,會直接導致程序崩潰。這時,我們需要在plist文件中加上相機權(quán)限的描述

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

本文的demo:二維碼的掃描

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,818評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,185評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,656評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,647評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,446評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,951評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,041評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,189評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,718評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,602評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,800評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,045評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,419評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,420評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,755評論 2 371

推薦閱讀更多精彩內(nèi)容