在ios7苹果推出了二维码扫描,以前想要做二维码扫描,只能通过第三方ZBar与ZXing。
ZBar在扫描的灵敏度上,和内存的使用上相对于ZXing上都是较优的,但是对于 “圆角二维码” 的扫描确很困难。
ZXing 是 Google Code上的一个开源的条形码扫描库,是用java设计的,连Google Glass 都在使用的。但有人为了追求更高效率以及可移植性,出现了c++ port. Github上的Objectivc-C port,其实就是用OC代码封装了一下而已,而且已经停止维护。这样效率非常低,在instrument下面可以看到CPU和内存疯涨,在内存小的机器上很容易崩溃。
AVFoundation无论在扫描灵敏度和性能上来说都是最优的。
首先要导入#import <AVFoundation/AVFoundation.h>框架
其次还需要授权应用可以访问相机
// 判断相机是否授权使用相机AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];if(status == AVAuthorizationStatusAuthorized) {} else if(status == AVAuthorizationStatusDenied){// NSLog(@"denied不允许");return ;} else if(status == AVAuthorizationStatusNotDetermined){[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {if(granted){ // NSLog(@"允许");} else { // NSLog(@"不允许");return;}}];}// typedef enum // AVAuthorizationStatusNotDetermined = 0, // 用户尚未做出选择这个应用程序的问候 // AVAuthorizationStatusRestricted, // 此应用程序没有被授权访问的照片数据。可能是家长控制权限 // AVAuthorizationStatusDenied, // 用户已经明确否认了这一照片数据的应用程序访问 // AVAuthorizationStatusAuthorized // 用户已经授权应用访问照片数据} CLAuthorizationStatus;
完成二维码扫描大致有十个步骤:
// 1.获取输入设备AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];// 2.创建输入对象NSError *error;AVCaptureDeviceInput *inPut = [[AVCaptureDeviceInput alloc] initWithDevice:device error:&error];if (inPut == nil) {UIAlertView *aler = [[UIAlertView alloc] initWithTitle:@"提示" message:@"设备不可用" delegate:nil cancelButtonTitle:nil otherButtonTitles:@"确定", nil];[self.view addSubview:aler];[aler show];return;}// 3.创建输出对象AVCaptureMetadataOutput *outPut = [[AVCaptureMetadataOutput alloc] init];// 4.设置代理监听输出对象的输出流 (说明:使用主线程队列,相应比较同步,使用其他队列,相应不同步,容易让用户产生不好的体验) [outPut setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];// 5.创建会话AVCaptureSession *session = [[AVCaptureSession alloc] init];self.session = session;// 6.将输入和输出对象添加到会话if ([session canAddInput:inPut]) {[session addInput:inPut];}if ([session canAddOutput:outPut]) {[session addOutput:outPut];}// 7.告诉输出对象, 需要输出什么样的数据 // 提示:一定要先设置会话的输出为output之后,再指定输出的元数据类型! [outPut setMetadataObjectTypes:@[AVMetadataObjectTypeQRCode]];// 8.创建预览图层AVCaptureVideoPreviewLayer *preViewLayer = [AVCaptureVideoPreviewLayer layerWithSession:session];preViewLayer.frame = self.view.bounds;[self.view.layer insertSublayer:preViewLayer atIndex:0];// 9.设置扫面范围outPut.rectOfInterest = CGRectMake(0.2, 0.18, 0.6, 0.5);// 10.设置扫描框UIView *boxView = [[UIView alloc] initWithFrame:CGRectMake(0.2 * SrceenW, 0.18 * SrceenH, 0.6 * SrceenW, 0.5 * SrceenH)];self.boxView = boxView;boxView.layer.borderColor = [UIColor yellowColor].CGColor;boxView.layer.borderWidth = 3;[self.view addSubview:boxView];// 设置扫描线CALayer *scanLayer = [[CALayer alloc] init];self.scanLayer = scanLayer;scanLayer.frame = CGRectMake(0, 0, boxView.bounds.size.width, 2);scanLayer.backgroundColor = [UIColor redColor].CGColor;[boxView.layer addSublayer:scanLayer];// 开始扫描[session startRunning];
其中第9个步骤是可以优化内存的
@property(nonatomic) CGRect rectOfInterest;
这个属性大致意思就是告诉系统它需要注意的区域,大部分APP的扫码UI中都会有一个框,提醒你将条形码放入那个区域,这个属性的作用就在这里,它可以设置一个范围,只处理在这个范围内捕获到的图像的信息。如此一来,我们代码的效率又会得到很大的提高,在使用这个属性的时候。需要几点注意:
1、这个CGRect参数和普通的Rect范围不太一样,它的四个值的范围都是0-1,表示比例。
2、经过测试发现,这个参数里面的x对应的恰恰是距离左上角的垂直距离,y对应的是距离左上角的水平距离。
3、宽度和高度设置的情况也是类似。
/// 经过测试 使用rectOfInterest 更改扫描范围 并没有很好的可控制范围,如果想达到想微信那样,只有在固定的扫描框中才可以扫描成功
可以使用以下设置,在
-(void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection; 方法中,判断二维码的三个坐标点是否在扫描框中。
for (id objects in metadataObjects) {// 判断检测到的对象类型if (![objects isKindOfClass:[AVMetadataMachineReadableCodeObject class]]) {return;}// 转换对象坐标 AVMetadataMachineReadableCodeObject *obj = (AVMetadataMachineReadableCodeObject *)[preViewLayer transformedMetadataObjectForMetadataObject:objects];// 判断扫描范围if (!CGRectContainsRect(self.boxView.frame, obj.bounds)) {continue;}}
-----------------------------以下是源码:
#import "ScanQrcodeVController.h"
@protocol ScanQrcodeVControllerDelegate <NSObject> // 二维码返回结果 -(void)scanQrcodeWithNString:(NSString *) ruselt; @end @interface ScanQrcodeVController : UIViewController @property (nonatomic, weak) id<ScanQrcodeVControllerDelegate>delegate; @end
#import "ScanQrcodeVController.m"
@interface ScanQrcodeVController ()<AVCaptureMetadataOutputObjectsDelegate> // 会话 @property (nonatomic, strong) AVCaptureSession *session; // 定时器 @property (nonatomic, strong) CADisplayLink *link; // 扫描线 @property (nonatomic, strong) CALayer *scanLayer; // 扫描框 @property (nonatomic, weak) UIView *boxView; /// 保存二维码结果 @property (nonatomic, copy) NSString *string; @end@implementation ScanQrcodeVController - (void)viewDidLoad {[super viewDidLoad];self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"NavBack"] style:UIBarButtonItemStylePlain target:self action:@selector(goBack)];self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"确定" style:UIBarButtonItemStylePlain target:self action:@selector(doneClick)];[self scanCode]; }-(void)scanCode {CADisplayLink *link = [CADisplayLink displayLinkWithTarget:self selector:@selector(updataFrame)];self.link = link;link.frameInterval = 3;[link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
// 判断相机是否授权使用相机
AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
if(status == AVAuthorizationStatusAuthorized) {
} else if(status == AVAuthorizationStatusDenied){
// NSLog(@"denied不允许");
return ;
} else if(status == AVAuthorizationStatusNotDetermined){
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
if(granted){
// NSLog(@"允许");
} else {
// NSLog(@"不允许");
return;
}
}];
// 1.获取输入设备
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];// 2.创建输入对象NSError *error;AVCaptureDeviceInput *inPut = [[AVCaptureDeviceInput alloc] initWithDevice:device error:&error];if (inPut == nil) {UIAlertView *aler = [[UIAlertView alloc] initWithTitle:@"提示" message:@"设备不可用" delegate:nil cancelButtonTitle:nil otherButtonTitles:@"确定", nil];[self.view addSubview:aler];[aler show];return;}// 3.创建输出对象AVCaptureMetadataOutput *outPut = [[AVCaptureMetadataOutput alloc] init];// 4.设置代理监听输出对象的输出流 说明:使用主线程队列,相应比较同步,使用其他队列,相应不同步,容易让用户产生不好的体验 [outPut setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];// 5.创建会话AVCaptureSession *session = [[AVCaptureSession alloc] init];self.session = session;// 6.将输入和输出对象添加到会话if ([session canAddInput:inPut]) {[session addInput:inPut];}if ([session canAddOutput:outPut]) {[session addOutput:outPut];}// 7.告诉输出对象, 需要输出什么样的数据 // 提示:一定要先设置会话的输出为output之后,再指定输出的元数据类型! [outPut setMetadataObjectTypes:@[AVMetadataObjectTypeQRCode]];// 8.创建预览图层AVCaptureVideoPreviewLayer *preViewLayer = [AVCaptureVideoPreviewLayer layerWithSession:session];preViewLayer.frame = self.view.bounds;[self.view.layer insertSublayer:preViewLayer atIndex:0];// 9.设置扫面范围outPut.rectOfInterest = CGRectMake(0.2, 0.18, 0.6, 0.5);// 10.设置扫描框UIView *boxView = [[UIView alloc] initWithFrame:CGRectMake(0.2 * SrceenW, 0.18 * SrceenH, 0.6 * SrceenW, 0.5 * SrceenH)];self.boxView = boxView;boxView.layer.borderColor = [UIColor yellowColor].CGColor;boxView.layer.borderWidth = 3;[self.view addSubview:boxView];// 设置扫描线CALayer *scanLayer = [[CALayer alloc] init];self.scanLayer = scanLayer;scanLayer.frame = CGRectMake(0, 0, boxView.bounds.size.width, 2);scanLayer.backgroundColor = [UIColor redColor].CGColor;[boxView.layer addSublayer:scanLayer];// 开始扫描 [session startRunning]; }-(void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection {
for (id objects in metadataObjects) {
// 判断检测到的对象类型
if (![objects isKindOfClass:[AVMetadataMachineReadableCodeObject class]]) {
return;
}
// 转换对象坐标
AVMetadataMachineReadableCodeObject *obj = (AVMetadataMachineReadableCodeObject *)[preViewLayer transformedMetadataObjectForMetadataObject:objects];
// 判断扫描范围
if (!CGRectContainsRect(self.boxView.frame, obj.bounds)) {
continue;
}
// 设置代理
if ([self.delegate respondsToSelector:@selector(scanQrcodeWithNString:)]) {
[self.delegate scanQrcodeWithNString:obj.stringValue];
}
// 停止扫描
[self.session stopRunning];
// 移除CADisplayLink对象
[self.link removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
self.link = nil;
}
}-(void)updataFrame {CGRect frame = self.scanLayer.frame;if (self.scanLayer.frame.origin.y > self.boxView.frame.size.height) {frame.origin.y = -20;self.scanLayer.frame = frame;}else{frame.origin.y += 3;self.scanLayer.frame = frame;}}-(void)viewDidDisappear:(BOOL)animated{[super viewDidDisappear:animated];// 记得释放CADisplayLink对象if (self.link != nil) {[self.link invalidate];self.link = nil;} }// 返回上一个界面 -(void)goBack {[self.navigationController popViewControllerAnimated:YES]; }// 二维码扫描完成 -(void)doneClick {// 设置代理if ([self.delegate respondsToSelector:@selector(scanQrcodeWithNString:)]) {[self.delegate scanQrcodeWithNString:self.string];}[self.navigationController popToRootViewControllerAnimated:YES]; } @end