一、前言
最近在做一個關(guān)于掃描二維碼簽到的小東西,所以還是上來寫一篇關(guān)于二維碼的文章,網(wǎng)上也有一些掃描二維碼的框架,例如ZXing或者ZBar。但是感覺還不如用原生的好,所以果斷采用原生的了。本文介紹的二維碼的掃描,就是顯示二維碼掃描的結(jié)果,至于鏈接的跳轉(zhuǎn)和應用的打開,就不多說明,只要在plist文件和掃描的代理方法里面做處理就好了。
ps:
- 二維碼的掃描要調(diào)用相機,模擬器是不支持相機的,所以用模擬器測試的話,是會崩潰。
- 原生的二維碼掃描不支持圖像識別,只支持攝像頭掃描識別。
二、相關(guān)類的介紹
- AVCaptureDevice:代表抽象的硬件設(shè)備。
- AVCaptureDeviceInput:輸入設(shè)備
- AVCaptureMetadataOutput:輸出類,掃描的碼的類型均由這個類管理。
- AVCaptureSession:會話對象,連接輸入設(shè)備和輸出設(shè)備。
- 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:二維碼的掃描