最近有時(shí)間研究下iOS下的二維碼掃描索守,以前是用ZBar做掃描的,現(xiàn)在用系統(tǒng)原生的AVFoundation框架也可以做到了,下面分享一下自己的實(shí)現(xiàn)過程:
1.首先演示一下具體效果:
![演示地址](http://o8847agtd.bkt.clouddn.com/gif1.gif)
演示地址
2. 下面說一下實(shí)現(xiàn)過程:
. 1 首先需要創(chuàng)建二維碼掃描對象
// 顯示掃描后的結(jié)果
@property (weak, nonatomic) IBOutlet UILabel *resultLab;
// 高度約束
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *containerHeightCons;
// 掃描線
@property (weak, nonatomic) IBOutlet UIImageView *scanLineView;
// 掃描線的約束,這里很重要刻肄,動畫效果主要是根據(jù)設(shè)置這個的值實(shí)現(xiàn)的
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *scanLineCons;
// 自定義ToolBar
@property (weak, nonatomic) IBOutlet UITabBar *customTabBar;
// 會話
@property (nonatomic, strong) AVCaptureSession *session;
// 輸入設(shè)備
@property (nonatomic, strong) AVCaptureDeviceInput *deviceInput;
// 輸出設(shè)備
@property (nonatomic, strong) AVCaptureMetadataOutput *output;
// 預(yù)覽圖層
@property (nonatomic, strong) AVCaptureVideoPreviewLayer *previewLayer;
// 會話圖層
@property (nonatomic, strong) CALayer *drawLayer;
. 2 然后對這些對象進(jìn)行懶加載:
#pragma mark - 懶加載
// 會話
- (AVCaptureSession *)session
{
if (_session == nil) {
_session = [[AVCaptureSession alloc] init];
}
return _session;
}
// 拿到輸入設(shè)備
- (AVCaptureDeviceInput *)deviceInput
{
if (_deviceInput == nil) {
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
_deviceInput = [[AVCaptureDeviceInput alloc] initWithDevice:device error:nil];
}
return _deviceInput;
}
// 拿到輸出對象
- (AVCaptureMetadataOutput *)output
{
if (_output == nil) {
_output = [[AVCaptureMetadataOutput alloc] init];
}
return _output;
}
// 創(chuàng)建預(yù)覽圖層
- (AVCaptureVideoPreviewLayer *)previewLayer
{
if (_previewLayer == nil) {
_previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.session];
_previewLayer.frame = [UIScreen mainScreen].bounds;
}
return _previewLayer;
}
// 創(chuàng)建用于繪制邊線的圖層
- (CALayer *)drawLayer
{
if (_drawLayer == nil) {
_drawLayer = [[CALayer alloc] init];
_drawLayer.frame = [UIScreen mainScreen].bounds;
}
return _drawLayer;
}
. 3 創(chuàng)建完對象之后,我們先實(shí)現(xiàn)二維碼掃描的動態(tài)效果融欧,然后再去執(zhí)行掃描二維碼的操作:
// 這里主要是通過設(shè)置約束敏弃,讓掃描線不停的執(zhí)行動畫
- (void)startAnimation
{
// 讓約束從頂部開始
self.scanLineCons.constant = 0;
[self.view layoutIfNeeded];
// 設(shè)置動畫指定的次數(shù)
[UIView animateWithDuration:2.0 animations:^{
// 1.修改約束
self.scanLineCons.constant = self.containerHeightCons.constant;
[UIView setAnimationRepeatCount:MAXFLOAT];
// 2.強(qiáng)制更新界面
[self.view layoutIfNeeded];
}];
}
. 4 動畫實(shí)現(xiàn)之后,看了微信的二維碼掃描之后噪馏,發(fā)現(xiàn)下面有個toolbar麦到,可以掃描不同的類型,不同類型需要改變掃描View的高度逝薪,實(shí)現(xiàn)過程如下
/**
* 選擇tabBar時(shí)進(jìn)行跳轉(zhuǎn)
*
* @param tabBar tabbar
* @param item tabBar的item
*/
- (void)tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item
{
if (item.tag == 1) {
self.containerHeightCons.constant = 300;
} else {
self.containerHeightCons.constant = 150;
}
// 2.停止動畫
[self.view.layer removeAllAnimations];
[self.scanLineView.layer removeAllAnimations];
// 3.重新開始動畫
[self startAnimation];
}
. 4 下面是最重要的一步了隅要,就要開始二維碼掃描,這里需要判斷輸入輸出會話是否可以正確添加董济,添加完成之后步清,需要設(shè)置二維碼支持的類型,一般默認(rèn)是全部虏肾,然后還需要添加預(yù)覽圖層:
- (void)startScan
{
// 1.判斷是否能夠?qū)⑤斎胩砑拥綍捴? if (![self.session canAddInput:self.deviceInput]) {
return;
}
// 2.判斷是否能夠?qū)⑤敵鎏砑拥綍捴? if (![self.session canAddOutput:self.output]) {
return;
}
// 3.將輸入和輸出都添加到會話中
[self.session addInput:self.deviceInput];
[self.session addOutput:self.output];
// 4.設(shè)置輸出能夠解析的數(shù)據(jù)類型
// 注意: 設(shè)置能夠解析的數(shù)據(jù)類型, 一定要在輸出對象添加到會員之后設(shè)置, 否則會報(bào)錯
self.output.metadataObjectTypes = self.output.availableMetadataObjectTypes;
[self.output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
// 如果想實(shí)現(xiàn)只掃描一張圖片, 那么系統(tǒng)自帶的二維碼掃描是不支持的
// 只能設(shè)置讓二維碼只有出現(xiàn)在某一塊區(qū)域才去掃描
// self.output.rectOfInterest = CGRectMake(0.0, 0.0, 1, 1);
// 5.添加預(yù)覽圖層
[self.view.layer insertSublayer:self.previewLayer atIndex:0];
// 添加繪制圖層
[self.previewLayer addSublayer:self.drawLayer];
// 6.告訴session開始掃描
[self.session startRunning];
}
. 5 二維碼掃描獲得信息時(shí)廓啊,就會執(zhí)行下面的代理方法,我們可以在下面的代理方法中執(zhí)行我們需要執(zhí)行的操作:
/**
* 當(dāng)從二維碼中獲取到信息時(shí)封豪,就會調(diào)用下面的方法
*
* @param captureOutput 輸出對象
* @param metadataObjects 信息
* @param connection
*/
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection
{
// 0.清空圖層
[self clearCorners];
if (metadataObjects.count == 0 || metadataObjects == nil) {
return;
}
// 1.獲取掃描到的數(shù)據(jù)
// 注意: 要使用stringValue
self.resultLab.text = [metadataObjects.lastObject stringValue];
[self.resultLab sizeToFit];
// 2.獲取掃描到的二維碼的位置
// 2.1轉(zhuǎn)換坐標(biāo)
for (AVMetadataObject *object in metadataObjects) {
// 2.1.1判斷當(dāng)前獲取到的數(shù)據(jù), 是否是機(jī)器可識別的類型
if ([object isKindOfClass:[AVMetadataMachineReadableCodeObject class]]) {
// 2.1.2將坐標(biāo)轉(zhuǎn)換界面可識別的坐標(biāo)
AVMetadataMachineReadableCodeObject *codeObject = (AVMetadataMachineReadableCodeObject *)[self.previewLayer transformedMetadataObjectForMetadataObject:object];
// 2.1.3繪制圖形
[self drawCorners:codeObject];
}
}
}
. 6 對掃描到的二維碼谴轮,我們可以畫出它的具體位置:
/**
* 畫出二維碼的邊框
*
* @param codeObject 保存了坐標(biāo)的對象
*/
- (void)drawCorners:(AVMetadataMachineReadableCodeObject *)codeObject
{
if (codeObject.corners.count == 0) {
return;
}
// 1.創(chuàng)建一個圖層
CAShapeLayer *layer = [[CAShapeLayer alloc] init];
layer.lineWidth = 4;
layer.strokeColor = [UIColor redColor].CGColor;
layer.fillColor = [UIColor clearColor].CGColor;
// 2.創(chuàng)建路徑
UIBezierPath *path = [[UIBezierPath alloc] init];
CGPoint point = CGPointZero;
NSInteger index = 0;
// 2.1移動到第一個點(diǎn)
// 從corners數(shù)組中取出第0個元素, 將這個字典中的x/y賦值給point
CGPointMakeWithDictionaryRepresentation((CFDictionaryRef)codeObject.corners[index++], &point);
[path moveToPoint:point];
// 2.2移動到其它的點(diǎn)
while (index < codeObject.corners.count) {
CGPointMakeWithDictionaryRepresentation((CFDictionaryRef)codeObject.corners[index++], &point);
[path addLineToPoint:point];
}
// 2.3關(guān)閉路徑
[path closePath];
// 2.4繪制路徑
layer.path = path.CGPath;
// 3.將繪制好的圖層添加到drawLayer上
[self.drawLayer addSublayer:layer];
}
/**
* 清除邊線
*/
- (void)clearCorners
{
if (self.drawLayer.sublayers == nil || self.drawLayer.sublayers.count == 0) {
return;
}
for (CALayer *subLayer in self.drawLayer.sublayers) {
[subLayer removeFromSuperlayer];
}
}
3.這里面也有一些注意事項(xiàng):
. 確保輸入輸出對象被正確的添加到會話中
. 實(shí)現(xiàn)動畫主要是設(shè)置掃描線的頂部距離父控件的頂部的高度實(shí)現(xiàn)的