前面我們介紹了二維碼的生成坟冲,現在我們介紹一下怎么掃描二維碼舞箍;在iOS7之前,大部分應用中使用的二維碼掃描是ZXing或者ZBar第三方的掃描框架蜂大。在iOS7之后闽铐,蘋果自身提供了二維碼的掃描功能,從效率上來說奶浦,原生的二維碼遠高于這些第三方框架兄墅,現在就看看類似與微信掃一掃的界面的實現;
??實現掃描二維碼澳叉,首先要調起系統攝像頭創(chuàng)建視頻會話隙咸,所以需要導入AVFoundation框架;還要用到以下幾個類:
@property (strong,nonatomic)AVCaptureDevice *device; // 捕捉硬件設備
@property (strong,nonatomic)AVCaptureDeviceInput *input; // 外界成像輸入配置
@property (strong,nonatomic)AVCaptureMetadataOutput *output; // 成像輸出配置
@property (strong,nonatomic)AVCaptureSession *session; // 捕捉設置
@property (strong,nonatomic)AVCaptureVideoPreviewLayer *prelayer; // 圖層布局層
@property (nonatomic, retain) UIImageView *line; // 掃描動畫線
1成洗、AVCaptureDevice:捕捉硬件設備五督;
2、AVCaptureDeviceInput: 外界成像輸入配置瓶殃。這個類用來表示輸入數據的硬件設備充包,配置抽象設備的port;
3遥椿、AVCaptureMetadataOutput: 成像輸出配置基矮。這個支持二維碼、條形碼等圖像數據的識別冠场;
4家浇、AVCaptureSession: 捕捉設置,此類作為硬件設備輸入輸出信息的橋梁碴裙,承擔實時獲取設備數據的責任钢悲;
5、AVCaptureVideoPreviewLayer: 圖層類舔株。用來快速呈現攝像頭獲取的原始數據二維碼掃描功能的實現步驟是創(chuàng)建好會話對象莺琳,用來獲取從硬件設備輸入的數據,并實時顯示在界面上督笆;
下面是具體實現和創(chuàng)建方法芦昔,在創(chuàng)建前先檢測下攝像頭有沒有允許開啟:
- (void)setupCamera
{
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
if (device==nil){
UIAlertView * alert = [[UIAlertView alloc]initWithTitle:@"溫馨提示" message:@"攝像頭未開啟" delegate:nil cancelButtonTitle:@"確定" otherButtonTitles:nil, nil];
[alert show];
return;
}
// Device (獲取手機設備的硬件 —— 攝像頭)
_device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
// Input (獲取手機攝像頭的輸入流設置)
_input = [AVCaptureDeviceInput deviceInputWithDevice:_device error:nil];
// Output (獲取手機攝像頭的輸出流設置)
_output = [[AVCaptureMetadataOutput alloc]init];
// 設置輸出流協議 AVCaptureMetadataOutputObjectsDelegate
[_output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
// Session 硬件配置設置
_session = [[AVCaptureSession alloc]init];
[_session setSessionPreset:AVCaptureSessionPresetHigh];
// 加入硬件配置的輸入輸出流
if ([_session canAddInput:self.input])
{
[_session addInput:self.input];
}
if ([_session canAddOutput:self.output])
{
[_session addOutput:self.output];
}
// 條碼類型 AVMetadataObjectTypeQRCode
if ([self.output.availableMetadataObjectTypes containsObject:AVMetadataObjectTypeQRCode])
{
self.output.metadataObjectTypes = @[AVMetadataObjectTypeQRCode,AVMetadataObjectTypeEAN13Code];
}
// Preview
dispatch_async(dispatch_get_main_queue(), ^{
// 更新界面
_prelayer =[AVCaptureVideoPreviewLayer layerWithSession:self.session];
_prelayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
_prelayer.frame = CGRectMake(0, 64, SCREEN_WIDTH, SCREEN_HEIGHT-64);
[self.view.layer insertSublayer:self.prelayer atIndex:0];
// Start 開啟攝像頭運行
[_session startRunning];
});
}
設置攝像頭拍照的范圍就是_prelayer.frame
這個方法,注意,這是拍照的范圍娃肿,并不是掃描的范圍咕缎,要想限制二維碼掃描區(qū)域珠十,讓用戶能夠準確對準某個二維碼,不讓整個屏幕都是掃描區(qū)域凭豪,像微信一樣設置區(qū)域在框內焙蹭,需要設置一個參數rectOfInterest
,這個參數有點特別嫂伞,這個參數的rect 與平常設置的坐標系是完全相反的孔厉,原點坐標在右上角,即X與Y互換帖努、W與H互換撰豺,這個屬性的每一個值取值范圍在0~1之間;先設置掃描區(qū)域的寬拼余、高以及屏幕寬污桦、高的宏定義:
#define SCREEN_WIDTH [UIScreen mainScreen].bounds.size.width
#define SCREEN_HEIGHT [UIScreen mainScreen].bounds.size.height
#define scanWidth (SCREEN_WIDTH - 120)
#define scanHeight scanWidth
#define scanX (SCREEN_WIDTH - scanWidth)/2
#define scanY (SCREEN_HEIGHT - scanHeight)/2
給output設置掃描區(qū)域:
//設置掃描區(qū)域
CGFloat x = scanX/SCREEN_WIDTH;
CGFloat y = scanY/SCREEN_HEIGHT;
CGFloat width = scanWidth/SCREEN_WIDTH;
CGFloat height = scanHeight/SCREEN_HEIGHT;
//y 與 x 互換 width 與 height 互換
[_output setRectOfInterest:CGRectMake(y,x, height, width)];
到這,掃描已經準備完畢匙监,現在實現掃描動畫凡橱,使線循環(huán)上下運動;首先定義一個全局變量亭姥,掃描線和定時器:
@property(assign, nonatomic) BOOL isUp;
@property(strong, nonatomic) NSTimer *timer;
初始化圖片框和掃描線:
-(void)initView{
_isUp = NO;
UIImageView * imageView = [[UIImageView alloc]initWithFrame:CGRectMake(scanX, scanY, scanWidth, scanHeight)];
imageView.image = [UIImage imageNamed:@"watch_sao_kuang"];
[self.view addSubview:imageView];
_line = [[UIImageView alloc] initWithFrame:CGRectMake(scanX, scanY+5, scanWidth, 2)];
_line.image = [UIImage imageNamed:@"watch_sao_line"];
[self.view addSubview:_line];
_timer = [NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:@selector(repeatTime) userInfo:nil repeats:YES];
}
給定時器設置為0.01秒稼钩,線移動到上下間隔5個像素,實現動畫:
-(void)repeatTime
{
CGRect rect = _line.frame;
if (_isUp == NO) {
rect.origin.y += 1;
if (rect.origin.y >= scanY + scanHeight - 5 - rect.size.height)
{
_isUp = YES;
}
} else {
rect.origin.y -= 1;
if (rect.origin.y <= scanY + 5)
{
_isUp = NO;
}
}
_line.frame = rect;
}
運行效果如下:
??看看上面的微信掃一掃界面达罗,似乎還差一步坝撑,就是非掃描區(qū)域和掃描區(qū)域顏色有些區(qū)分,顏色暗一點氮块,有同學說這個簡單绍载,在非掃描區(qū)域覆蓋上、下滔蝉、左、右4個View就行塔沃,是的蝠引,可以這樣做,我第一次寫項目也是這么做的蛀柴,不難螃概,就是坐標算起來稍微復雜點,在這里介紹一種更為簡單的方法鸽疾,使用
CAShapeLayer
動畫吊洼,CAShapeLayer
是一個通過矢量圖形而不是 bitmap 來繪制的圖層子類≈瓢梗可以指定顏色冒窍、線寬等屬性递沪,用CGPath 來定義想要繪制的圖形,最后 CAShapeLayer 就會自動渲染出來综液。主要思路是設置繪制的路徑及顏色款慨,然后進行繪畫填充,代碼如下:
- (void)setshapeLayer{
_shapeLayer = [[CAShapeLayer alloc] init];
CGMutablePathRef path = CGPathCreateMutable();
//添加蒙版
CGPathAddRect(path, nil, self.view.bounds);
CGPathAddRect(path, nil, CGRectMake(scanX, scanY, scanWidth, scanHeight));
//填充規(guī)則
_shapeLayer.fillRule = kCAFillRuleEvenOdd;
//繪制的路徑
_shapeLayer.path = path;
//路徑中的填充顏色
_shapeLayer.fillColor = [UIColor blackColor].CGColor;
//設置透明度
_shapeLayer.opacity = 0.5;
[_shapeLayer setNeedsDisplay];
[self.view.layer addSublayer:_shapeLayer];
}
有幾個關鍵屬性谬莹,就是添加蒙版CGPathAddRect
,可以看到我們添加了兩次蒙版檩奠,為什么呢?因為除了中間的掃描區(qū)域之外附帽,其他的全部要置灰埠戳,所以要添加兩條路徑,一條是屏幕大小蕉扮,還有一條是掃描區(qū)域大小乞而。然后將路徑賦給CAShapeLayer的path屬性;想清楚這個規(guī)則慢显,這就要還得看看填充規(guī)則屬性fillRule
爪模,它有兩個屬性:kCAFillRuleNonZero
和kCAFillRuleEvenOdd
:
- kCAFillRuleNonZero:默認值,非零規(guī)則荚藻,當這個點作任意方法的射線屋灌,然后看射線和路徑的交點方向,選擇一個作為基準方向应狱,如果方向一致則加1共郭,方向不一致則減1。為0時疾呻,點不在路徑內除嘹。
- kCAFillRuleEvenOdd:奇偶規(guī)則,當這個點作任意方法的射線岸蜗,射線和路徑的交點數量是奇數則認為點在內部尉咕。
我們選擇的是kCAFillRuleEvenOdd
奇偶規(guī)則,意思就是添加蒙版的地方隨意找個點畫射線,與蒙版路徑相交的點數是奇還是偶璃岳,奇數代表是內部年缎,偶數代表是外部,內部填充顏色铃慷,外部不管单芜。可能你還不是很理解犁柜,下面我畫了一張圖洲鸠,更加直觀:
由圖可知,1馋缅、2代表大框除了小框的部分區(qū)域扒腕,就是我們的非掃描區(qū)域绢淀,看出無論從哪個方向畫射線,交點數都是基數個袜匿,黃點代表焦點數更啄,1有一個交點,2有三個交點居灯;小框內部部分祭务,就是我們的掃描區(qū)域,任意畫出的射線都是二個交點怪嫌,如線3义锥;所以CAShapeLayer自動識別,實現了中間的掃描區(qū)域之外岩灭,其他的全部要置灰拌倍;得到的效果圖如下:
??所有的準備工作都做完了,現在是驗收階段了噪径,只要我們包含了
<AVCaptureMetadataOutputObjectsDelegate>
代理就可以通過代理方法獲取掃描到的東西了:
#pragma mark AVCaptureMetadataOutputObjectsDelegate
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection
{
NSString *stringValue;
// 元數據對象
if ([metadataObjects count] >0)
{
// 可讀碼對象
AVMetadataMachineReadableCodeObject * metadataObject = [metadataObjects objectAtIndex:0];
stringValue = metadataObject.stringValue;
// 掃描成功后調用柱恤,解析二維碼
[_timer setFireDate:[NSDate distantFuture]];
[_session stopRunning];
NSLog(@"掃一掃結果:%@",stringValue);
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"掃描結果" message:stringValue preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"確認" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
if (_session != nil && _timer != nil) {
[_session startRunning];
[_timer setFireDate:[NSDate date]];
}
}]];
[self presentViewController:alert animated:YES completion:nil];
}
}
到這里掃描的頁面全部完成了。有幾點值得注意的地方找爱,當掃描成功之后梗顺,已經執(zhí)行了[_session stopRunning];
所以當我進入下個頁面返回或者左滑又松開的時候頁面會卡死,想要重新掃描车摄,必須重新開啟寺谤,還有定時器也需要重新開啟,我們可以放在viewWillAppear
方法開啟:
- (void)viewDidLoad {
[super viewDidLoad];
self.title = @"掃一掃";
self.view.backgroundColor = [UIColor whiteColor];
[self initView];
[self setshapeLayer];
[self setupCamera];
}
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[_timer setFireDate:[NSDate date]];
[_session startRunning];
}
-(void)viewDidDisappear:(BOOL)animated{
[super viewDidDisappear:animated];
[_session stopRunning];
}
聲明: 轉載請注明出處http://www.reibang.com/p/34c66cd69df9