昨天收到一個需求要給項目增加一個二維碼掃描的功能啤它,然后就找了一些AVFoundation的資料拼拼湊湊把功能完成了,中間也遇到了一些小坑舱痘。想了下轉(zhuǎn)IOS也有一段時間了好久不發(fā)博客了变骡,就寫一篇博客當做時給自己成長的一次記錄吧!同時也分享下代碼和大家一起學習進步芭逝,有哪里做得不到位的還希望各位大神多指教塌碌。
廢話不多說,先來說下大致的需求旬盯,首先實現(xiàn)二維碼的掃描識別功能 誊爹,頁面仿造微信掃碼,需求如下圖:
說下大致實現(xiàn)思路:
UI:基本比較簡單整個圖層的下方是從攝像頭采集到的圖像瓢捉,沒關鍵難點就在于四周半透明中間全透明的視圖,以及掃描的線條办成。
關于半透明視圖:我的實現(xiàn)方式是創(chuàng)建一個全透明的view定義好一個識別區(qū)然后在識別區(qū)的上下左右用Quartz2D分別畫4個半透明的矩形某弦。感覺比較挫靶壮,本來想用Quartz2d的API處理下,愣是只找到截取沒找到裁圖螃壤,希望有更好思路的大神可以指教下奸晴!
//填充區(qū)域顏色
[[UIColor colorWithRed:0 green:0 blue:0 alpha:0.65] set];
//掃碼區(qū)域上面填充
CGRect notScanRect = CGRectMake(0, 0, self.frame.size.width, _scanFrame.origin.y);
CGContextFillRect(context, notScanRect);
//掃碼區(qū)域左邊填充
rect = CGRectMake(0, _scanFrame.origin.y, _scanFrame.origin.x,_scanFrame.size.height);
CGContextFillRect(context, rect);
//掃碼區(qū)域右邊填充
rect = CGRectMake(CGRectGetMaxX(_scanFrame), _scanFrame.origin.y, _scanFrame.origin.x,_scanFrame.size.height);
CGContextFillRect(context, rect);
//掃碼區(qū)域下面填充
rect = CGRectMake(0, CGRectGetMaxY(_scanFrame), self.frame.size.width,self.frame.size.height - CGRectGetMaxY(_scanFrame));
CGContextFillRect(context, rect);
掃描的線條動畫:這個實現(xiàn)的方式多種多樣,我就說下我的實現(xiàn)涕刚,就是一條美工提供好的線條然后用timer調(diào)setNeedsDisplay副女。在drawRect中改變圖片的frame 再繪制到界面上。
@interface QRCodeAreaView()
/**
*? 記錄當前線條繪制的位置
*/
@property (nonatomic,assign) CGPoint position;
/**
*? 定時器
*/
@property (nonatomic,strong)NSTimer? *timer;
@end
@implementation QRCodeAreaView
- (void)drawRect:(CGRect)rect {
CGPoint newPosition = self.position;
newPosition.y += 1;
//判斷y到達底部沟涨,從新開始下降
if (newPosition.y > rect.size.height) {
newPosition.y = 0;
}
//重新賦值position
self.position = newPosition;
// 繪制圖片
UIImage *image = [UIImage imageNamed:@"line"];
[image drawAtPoint:self.position];
}
-(instancetype)initWithFrame:(CGRect)frame{
if (self = [super initWithFrame:frame]) {
self.backgroundColor = [UIColor clearColor];
UIImageView *areaView = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"frame_icon"]];
areaView.width = self.width;
areaView.height = self.height;
[self addSubview:areaView];
self.timer = [NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:@selector(setNeedsDisplay) userInfo:nil repeats:YES];
}
return self;
}
-(void)startAnimaion{
[self.timer setFireDate:[NSDate date]];
}
-(void)stopAnimaion{
[self.timer setFireDate:[NSDate distantFuture]];
}
@end
說下二維碼的識別:借助AVfoundation的強大API整體實現(xiàn)還是相當簡單的诀浪,識別效率真的是杠杠的睛竣。
/**
*? 初始化二維碼掃描
*/
//獲取攝像設備
AVCaptureDevice * device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
//創(chuàng)建輸入流
AVCaptureDeviceInput * input = [AVCaptureDeviceInput deviceInputWithDevice:device error:nil];
//創(chuàng)建輸出流
AVCaptureMetadataOutput * output = [[AVCaptureMetadataOutput alloc]init];
//設置代理 在主線程里刷新
[output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
//設置識別區(qū)域
//深坑,這個值是按比例0~1設置验夯,而且X挥转、Y要調(diào)換位置,width域仇、height調(diào)換位置
output.rectOfInterest = CGRectMake(_areaView.y/screen_height, _areaView.x/screen_width, _areaView.height/screen_height, _areaView.width/screen_width);
//初始化鏈接對象
session = [[AVCaptureSession alloc]init];
//高質(zhì)量采集率
[session setSessionPreset:AVCaptureSessionPresetHigh];
[session addInput:input];
[session addOutput:output];
//設置掃碼支持的編碼格式(如下設置條形碼和二維碼兼容)
output.metadataObjectTypes=@[AVMetadataObjectTypeQRCode,AVMetadataObjectTypeEAN13Code, AVMetadataObjectTypeEAN8Code, AVMetadataObjectTypeCode128Code];
AVCaptureVideoPreviewLayer * layer = [AVCaptureVideoPreviewLayer layerWithSession:session];
layer.videoGravity=AVLayerVideoGravityResizeAspectFill;
layer.frame=self.view.layer.bounds;
[self.view.layer insertSublayer:layer atIndex:0];
//開始捕獲
[session startRunning];
#pragma 二維碼掃描的回調(diào)
-(void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection{
if (metadataObjects.count>0) {
[session stopRunning];//停止掃描
[_areaView stopAnimaion];//暫停動畫
AVMetadataMachineReadableCodeObject * metadataObject = [metadataObjects objectAtIndex : 0 ];
//輸出掃描字符串
NSLog(@"%@",metadataObject.stringValue);
}
}
但是中間也有一個坑就是眾所周知的rectOfInterest屬性,真想吐槽下apple干嘛不弄個正常人思維的東西择镇?第一次使用的朋友同時又想提高識別效率腻豌,做出和大平臺那樣的中間區(qū)域識別的效果一看rect肯定和我一樣興奮,不就是設置個rect嘛苏携。按照正常rect設置完成后右冻,你會發(fā)現(xiàn)你的程序失效了。點開這個方法的幫助不難看到
The default value of this property is the value CGRectMake(0, 0, 1, 1).? Metadata objects whose bounds do not intersect with the rectOfInterest will not be returned.
官方這是在告訴你這個rect范圍時0~1跪但,我們立馬能想到計算X比例不就是當前X坐標/屏幕寬度嘛忆首?可是事實不是详幽,這個就是最操蛋的地方唇聘。我百度了一個多小時終于搞明白了(當初學習的博客找不到了,過后找到了再注明轉(zhuǎn)載)宪肖,原來這個XY是要翻過來的么介,同時width height也是要反過來壤短,于是這個rect應該是CGRectMake(Y/屏幕高度, X/屏幕高度,heidth/屏幕高度,width/屏幕寬度)。最最坑爹的是這個設置設置完了以后屏幕上沒有任何可視效果桶现。我也是在中間畫了一個半透明矩形拿一個二維碼各個角度識別才測試出來的骡和。
//設置識別區(qū)域
//深坑,這個值是按比例0~1設置婆赠,而且X、Y要調(diào)換位置妙黍,width、height調(diào)換位置
output.rectOfInterest = CGRectMake(_areaView.y/screen_height, _areaView.x/screen_width, _areaView.height/screen_height, _areaView.width/screen_width);
//掃描區(qū)域
CGRect areaRect = CGRectMake((screen_width - 218)/2, (screen_height - 218)/2, 218, 218);
完整項目地址:https://github.com/CharmingLee/QRCodeController.git
https://git.oschina.net/CharmingLee/QRCodeController.git