须知:
- 在 iOS7 以前,在iOS中实现二维码和条形码扫描,我们所知的有,两大开源组件ZBar与ZXing. 这两大组件我们都有用过,这里总结下各自的缺点:
- 1 .ZBar在扫描的灵敏度上,和内存的使用上相对于ZXing上都是较优的,但是对于 “圆角二维码” 的扫描确很困难
- 2 .ZXing 是 Google Code上的一个开源的条形码扫描库,是用java设计的,连Google Glass 都在使用的。但有人为了追求更高效率以及可移植性,出现了c++ port. Github上的Objectivc-C port,其实就是用OC代码封装了一下而已,而且已经停止维护。这样效率非常低,在instrument下面可以看到CPU和内存疯涨,在内存小的机器上很容易崩溃
- 3 .AVFoundation无论在扫描灵敏度和性能上来说都是最优的,所以毫无疑问我们应该切换到AVFoundation,需要兼容iOS 6或之前的版本可以用zbar或zxing代替
0.搭建UI界面,如图
Snip20151209_1.png1.导入框架
#import <AVFoundation/AVFoundation.h>
2.声明属性,连线,设置需要的代理
@interface ViewController ()<UITabBarDelegate,AVCaptureMetadataOutputObjectsDelegate,UINavigationControllerDelegate,UIImagePickerControllerDelegate>
@property ( strong , nonatomic ) AVCaptureDevice * device;
@property ( strong , nonatomic ) AVCaptureDeviceInput * input;
@property ( strong , nonatomic ) AVCaptureMetadataOutput * output;
@property ( strong , nonatomic ) AVCaptureSession * session;
@property ( strong , nonatomic ) AVCaptureVideoPreviewLayer * previewLayer;
/*** 专门用于保存描边的图层 ***/
@property (nonatomic,strong) CALayer *containerLayer;
@end
3.调用开始扫描设置方法
- (void)viewDidLoad {
[super viewDidLoad];
// 开始扫描二维码
[self startScan];
}
4.属性的懒加载
#pragma mark -------- 懒加载---------
- (AVCaptureDevice *)device
{
if (_device == nil) {
_device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
}
return _device;
}
- (AVCaptureDeviceInput *)input
{
if (_input == nil) {
_input = [AVCaptureDeviceInput deviceInputWithDevice:self.device error:nil];
}
return _input;
}
- (AVCaptureSession *)session
{
if (_session == nil) {
_session = [[AVCaptureSession alloc] init];
}
return _session;
}
- (AVCaptureVideoPreviewLayer *)previewLayer
{
if (_previewLayer == nil) {
_previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:self.session];
}
return _previewLayer;
}
// 设置输出对象解析数据时感兴趣的范围
// 默认值是 CGRect(x: 0, y: 0, width: 1, height: 1)
// 通过对这个值的观察, 我们发现传入的是比例
// 注意: 参照是以横屏的左上角作为, 而不是以竖屏
// out.rectOfInterest = CGRect(x: 0, y: 0, width: 0.5, height: 0.5)
- (AVCaptureMetadataOutput *)output
{
if (_output == nil) {
_output = [[AVCaptureMetadataOutput alloc] init];
// 1.获取屏幕的frame
CGRect viewRect = self.view.frame;
// 2.获取扫描容器的frame
CGRect containerRect = self.customContainerView.frame;
CGFloat x = containerRect.origin.y / viewRect.size.height;
CGFloat y = containerRect.origin.x / viewRect.size.width;
CGFloat width = containerRect.size.height / viewRect.size.height;
CGFloat height = containerRect.size.width / viewRect.size.width;
// CGRect outRect = CGRectMake(x, y, width, height);
// [_output rectForMetadataOutputRectOfInterest:outRect];
_output.rectOfInterest = CGRectMake(x, y, width, height);
}
return _output;
}
- (CALayer *)containerLayer
{
if (_containerLayer == nil) {
_containerLayer = [[CALayer alloc] init];
}
return _containerLayer;
}
5.开始扫描
- (void)startScan
{
// 1.判断输入能否添加到会话中
if (![self.session canAddInput:self.input]) return;
[self.session addInput:self.input];
// 2.判断输出能够添加到会话中
if (![self.session canAddOutput:self.output]) return;
[self.session addOutput:self.output];
// 4.设置输出能够解析的数据类型
// 注意点: 设置数据类型一定要在输出对象添加到会话之后才能设置
self.output.metadataObjectTypes = self.output.availableMetadataObjectTypes;
// 5.设置监听监听输出解析到的数据
[self.output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
// 6.添加预览图层
[self.view.layer insertSublayer:self.previewLayer atIndex:0];
self.previewLayer.frame = self.view.bounds;
// 7.添加容器图层
[self.view.layer addSublayer:self.containerLayer];
self.containerLayer.frame = self.view.bounds;
// 8.开始扫描
[self.session startRunning];
}
6.实现代理方法
#pragma mark --------AVCaptureMetadataOutputObjectsDelegate ---------
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection
{
// id 类型不能点语法,所以要先去取出数组中对象
AVMetadataMachineReadableCodeObject *object = [metadataObjects lastObject];
if (object == nil) return;
// 只要扫描到结果就会调用
self.customLabel.text = object.stringValue;
// 清除之前的描边
[self clearLayers];
// 对扫描到的二维码进行描边
AVMetadataMachineReadableCodeObject *obj = (AVMetadataMachineReadableCodeObject *)[self.previewLayer transformedMetadataObjectForMetadataObject:object];
// 绘制描边
[self drawLine:obj];
}
7.利用贝塞尔曲线绘制描边
- (void)drawLine:(AVMetadataMachineReadableCodeObject *)objc
{
NSArray *array = objc.corners;
// 1.创建形状图层, 用于保存绘制的矩形
CAShapeLayer *layer = [[CAShapeLayer alloc] init];
// 设置线宽
layer.lineWidth = 2;
// 设置描边颜色
layer.strokeColor = [UIColor greenColor].CGColor;
layer.fillColor = [UIColor clearColor].CGColor;
// 2.创建UIBezierPath, 绘制矩形
UIBezierPath *path = [[UIBezierPath alloc] init];
CGPoint point = CGPointZero;
int index = 0;
CFDictionaryRef dict = (__bridge CFDictionaryRef)(array[index++]);
// 把点转换为不可变字典
// 把字典转换为点,存在point里,成功返回true 其他false
CGPointMakeWithDictionaryRepresentation(dict, &point);
// 设置起点
[path moveToPoint:point];
// 2.2连接其它线段
for (int i = 1; i<array.count; i++) {
CGPointMakeWithDictionaryRepresentation((__bridge CFDictionaryRef)array[i], &point);
[path addLineToPoint:point];
}
// 2.3关闭路径
[path closePath];
layer.path = path.CGPath;
// 3.将用于保存矩形的图层添加到界面上
[self.containerLayer addSublayer:layer];
}
8.清除描边
- (void)clearLayers
{
if (self.containerLayer.sublayers)
{
for (CALayer *subLayer in self.containerLayer.sublayers)
{
[subLayer removeFromSuperlayer];
}
}
}
实现相册二维码识别
1.打开系统相册
- (IBAction)openCameralClick:(id)sender {
// 1.判断相册是否可以打开
if (![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary]) return;
// 2. 创建图片选择控制器
UIImagePickerController *ipc = [[UIImagePickerController alloc] init];
ipc.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
// 4.设置代理
ipc.delegate = self;
// 5.modal出这个控制器
[self presentViewController:ipc animated:YES completion:nil];
}
2.实现代理方法(注意需要遵守两个代理协议)
#pragma mark -------- UIImagePickerControllerDelegate---------
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info
{
// 1.取出选中的图片
UIImage *pickImage = info[UIImagePickerControllerOriginalImage];
NSData *imageData = UIImagePNGRepresentation(pickImage);
CIImage *ciImage = [CIImage imageWithData:imageData];
// 2.从选中的图片中读取二维码数据
// 2.1创建一个探测器
CIDetector *detector = [CIDetector detectorOfType:CIDetectorTypeQRCode context:nil options:@{CIDetectorAccuracy: CIDetectorAccuracyLow}];
// 2.2利用探测器探测数据
NSArray *feature = [detector featuresInImage:ciImage];
// 2.3取出探测到的数据
for (CIQRCodeFeature *result in feature) {
// NSLog(@"%@",result.messageString);
NSString *urlStr = result.messageString;
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:urlStr]];
}
// 注意: 如果实现了该方法, 当选中一张图片时系统就不会自动关闭相册控制器
[picker dismissViewControllerAnimated:YES completion:nil];
}
3.在界面消失的时候关闭session
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[self.session stopRunning];
}
效果图:
二维码.PNG
网友评论
[supportTypes removeObject:AVMetadataObjectTypeFace];
self.output.metadataObjectTypes = [supportTypes copy];
需要筛选一下支持的类型,不然会闪退,faceType没有stringValue
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection
{
// if (metadataObjects.count > 0) {
// id 类型不能点语法,所以要先去取出数组中对象
NSMutableArray *supportTypes = [self.output.availableMetadataObjectTypes mutableCopy];
[supportTypes removeObject:AVMetadataObjectTypeFace];
self.output.metadataObjectTypes = [supportTypes copy];
AVMetadataMachineReadableCodeObject *object = [metadataObjects lastObject];
if (object == nil) return;
// 只要扫描到结果就会调用
self.customLabel.text = object.stringValue;
[self clearLayers];