这两天用到了二维码扫描功能,网上第三方框架是有,但是想弄清楚其原理,就自己用了系统原生的方法,其中部分地方有点坑,就记录下来,权当下次使用提醒。
二维码扫描
不多说,上代码:
#import <AVFoundation/AVFoundation.h> //引用AVFoundation框架 @interface ViewController ()<AVCaptureMetadataOutputObjectsDelegate> //遵守AVCaptureMetadataOutputObjectsDelegate协议 @property ( strong , nonatomic ) AVCaptureDevice * device; //捕获设备,默认后置摄像头 @property ( strong , nonatomic ) AVCaptureDeviceInput * input; //输入设备 @property ( strong , nonatomic ) AVCaptureMetadataOutput * output;//输出设备,需要指定他的输出类型及扫描范围 @property ( strong , nonatomic ) AVCaptureSession * session; //AVFoundation框架捕获类的中心枢纽,协调输入输出设备以获得数据 @property ( strong , nonatomic ) AVCaptureVideoPreviewLayer * previewLayer;//展示捕获图像的图层,是CALayer的子类 @property (nonatomic,strong)UIView *scanView;定位扫描框在哪个位置
初始化对象:
- (AVCaptureDevice *)device { if (_device == nil) { // 设置AVCaptureDevice的类型为Video类型 _device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; } return _device; } - (AVCaptureDeviceInput *)input { if (_input == nil) { //输入设备初始化 _input = [AVCaptureDeviceInput deviceInputWithDevice:self.device error:nil]; } return _input; }
这里设置输出设备要注意
rectOfInterest
属性的设置,一般默认是CGRect(x: 0, y: 0, width: 1, height: 1)
,全屏都能读取的,但是读取速度较慢。注意rectOfInterest
属性的传人的是比例。比例是根据扫描容器的尺寸比上屏幕尺寸(注意要计算的时候要计算导航栏高度,有的话需减去)。
参照的是横屏左上角的比例,而不是竖屏。所以我们再设置的时候要调整方向如下面所示。- (AVCaptureMetadataOutput *)output { if (_output == nil) { //初始化输出设备 _output = [[AVCaptureMetadataOutput alloc] init]; // 1.获取屏幕的frame CGRect viewRect = self.view.frame; // 2.获取扫描容器的frame CGRect containerRect = self.scanView.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; //rectOfInterest属性设置设备的扫描范围 _output.rectOfInterest = CGRectMake(x, y, width, height); } return _output; }
网上还有一种是根据
AVCaptureInputPortFormatDescriptionDidChangeNotification
通知设置的,也是可行的,自选一种即可。__weak typeof(self) weakSelf = self; [[NSNotificationCenter defaultCenter]addObserverForName:AVCaptureInputPortFormatDescriptionDidChangeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) { if (weakSelf){ //调整扫描区域 AVCaptureMetadataOutput *output = weakSelf.session.outputs.firstObject; output.rectOfInterest = [weakSelf.previewLayer metadataOutputRectOfInterestForRect:weakSelf.scanView.frame]; } }];
下面初始化
AVCaptureSession
和AVCaptureVideoPreviewLayer
:- (AVCaptureSession *)session { if (_session == nil) { _session = [[AVCaptureSession alloc] init]; } return _session; } - (AVCaptureVideoPreviewLayer *)previewLayer { if (_previewLayer == nil) { //负责图像渲染出来 _previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:self.session]; self.previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill; } return _previewLayer; }
接着我们再
viewDidLoad
中初始化并启动扫描- (void)viewDidLoad { [super viewDidLoad]; CGFloat kScreen_Width = [UIScreen mainScreen].bounds.size.width; //定位扫描框在屏幕正中央,并且宽高为200的正方形 self.scanView = [[UIView alloc]initWithFrame:CGRectMake((kScreen_Width-200)/2, (self.view.frame.size.height-200)/2, 200, 200)]; [self.view addSubview:self.scanView]; //设置扫描界面(包括扫描界面之外的部分置灰,扫描边框等的设置),后面设置 TNWCameraScanView *clearView = [[TNWCameraScanView alloc]initWithFrame:self.view.frame]; [self.view addSubview:clearView]; [self startScan]; } - (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.设置输出能够解析的数据类型 // 注意点: 设置数据类型一定要在输出对象添加到会话之后才能设置 //设置availableMetadataObjectTypes为二维码、条形码等均可扫描,如果想只扫描二维码可设置为 // [self.output setMetadataObjectTypes:@[AVMetadataObjectTypeQRCode]]; 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; // 8.开始扫描 [self.session startRunning]; }
下面是接收扫描结果的代理
AVCaptureMetadataOutputObjectsDelegate
:- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection { [self.session stopRunning]; //停止扫描 //我们捕获的对象可能不是AVMetadataMachineReadableCodeObject类,所以要先判断,不然会崩溃 if (![[metadataObjects lastObject] isKindOfClass:[AVMetadataMachineReadableCodeObject class]]) { [self.session startRunning]; return; } // id 类型不能点语法,所以要先去取出数组中对象 AVMetadataMachineReadableCodeObject *object = [metadataObjects lastObject]; if ( object.stringValue == nil ){ [self.session startRunning]; }
上面就是常用的二维码扫描部分的代码,我们下面再简单的看下它扫描框之外界面的设置(包括扫描界面之外的部分置灰,扫描边框等)。具体样式你们可以自己调整添加。
扫描框
@interface TNWCameraScanView : UIView - (instancetype)initWithFrame:(CGRect)frame; @end
#import "TNWCameraScanView.h" @interface TNWCameraScanView() { CGFloat sceenHeight; NSTimer *timer; CGRect scanRect; CGFloat kScreen_Width; CGFloat kScreen_Height; } @property (nonatomic,assign)CGFloat lineWidth; @property (nonatomic,assign)CGFloat height; @property (nonatomic,strong)UIColor *lineColor; @property (nonatomic, assign)CGFloat scanTime; @end @implementation TNWCameraScanView - (instancetype)initWithFrame:(CGRect)frame{ if (self = [super initWithFrame:frame]) { self.backgroundColor = [UIColor clearColor]; // 清空背景色,否则为黑 sceenHeight =self.frame.size.height; _height = 200; // 宽高200的正方形 _lineWidth = 2; // 扫描框4个脚的宽度 _lineColor = [UIColor greenColor]; // 扫描框4个脚的颜色 _scanTime = 3; //扫描线的时间间隔设置 kScreen_Width = [UIScreen mainScreen].bounds.size.width; kScreen_Height = [UIScreen mainScreen].bounds.size.height; [self scanLineMove]; //定时,多少秒扫描线刷新一次 timer = [NSTimer scheduledTimerWithTimeInterval:_scanTime target:self selector:@selector(scanLineMove) userInfo:nil repeats:YES]; } return self; } - (void)scanLineMove{ UIView *line = [[UIView alloc]initWithFrame:CGRectMake((kScreen_Width-_height)/2, (sceenHeight-_height)/2, _height, 1)]; line.backgroundColor = [UIColor greenColor]; [self addSubview:line]; [UIView animateWithDuration:_scanTime animations:^{ line.frame = CGRectMake((kScreen_Width-_height)/2, (sceenHeight+_height)/2, _height, 0.5); } completion:^(BOOL finished) { [line removeFromSuperview]; }]; } -(void)drawRect:(CGRect)rect{ CGFloat bottomHeight = (sceenHeight-_height)/2; CGFloat leftWidth = (kScreen_Width-_height)/2; CGContextRef ctx = UIGraphicsGetCurrentContext(); //设置4个方向的灰度值,透明度为0.5,可自行调整。 CGContextSetRGBFillColor(ctx, 0, 0, 0, 0.5); CGContextFillRect(ctx, CGRectMake(0, 0, kScreen_Width, bottomHeight)); CGContextStrokePath(ctx); CGContextFillRect(ctx, CGRectMake(0,bottomHeight, leftWidth, _height)); CGContextStrokePath(ctx); CGContextFillRect(ctx, CGRectMake((kScreen_Width+_height)/2, bottomHeight, leftWidth, _height)); CGContextStrokePath(ctx); CGContextFillRect(ctx, CGRectMake(0,(sceenHeight+_height)/2, kScreen_Width, bottomHeight)); CGContextStrokePath(ctx); //扫描框4个脚的设置 CGContextSetLineWidth(ctx, _lineWidth); CGContextSetStrokeColorWithColor(ctx, _lineColor.CGColor); //左上角 CGContextMoveToPoint(ctx, leftWidth, bottomHeight+30); CGContextAddLineToPoint(ctx, leftWidth, bottomHeight); CGContextAddLineToPoint(ctx, leftWidth+30, bottomHeight); CGContextStrokePath(ctx); //右上角 CGContextMoveToPoint(ctx, (kScreen_Width+_height)/2-30, bottomHeight); CGContextAddLineToPoint(ctx, (kScreen_Width+_height)/2, bottomHeight); CGContextAddLineToPoint(ctx, (kScreen_Width+_height)/2, bottomHeight+30); CGContextStrokePath(ctx); //左下角 CGContextMoveToPoint(ctx, leftWidth, (sceenHeight+_height)/2-30); CGContextAddLineToPoint(ctx, leftWidth, (sceenHeight+_height)/2); CGContextAddLineToPoint(ctx, leftWidth+30, (sceenHeight+_height)/2); CGContextStrokePath(ctx); //右下角 CGContextMoveToPoint(ctx, (kScreen_Width+_height)/2-30, (sceenHeight+_height)/2); CGContextAddLineToPoint(ctx, (kScreen_Width+_height)/2, (sceenHeight+_height)/2); CGContextAddLineToPoint(ctx, (kScreen_Width+_height)/2, (sceenHeight+_height)/2-30); CGContextStrokePath(ctx); 设置扫描框4个边的颜色和线框。 // CGContextSetStrokeColorWithColor(ctx, [UIColor whiteColor].CGColor); // CGContextSet_lineWidth(ctx, 1); // CGContextAddRect(ctx, CGRectMake(leftWidth, bottomHeight, height, height)); // CGContextStrokePath(ctx); scanRect = CGRectMake(leftWidth, bottomHeight, _height, _height); } - (void)dealloc{ //清除计时器 [timer invalidate]; timer = nil; }
扫描相册中的二维码
再说一下关于扫描相册中的二维码部分,使用
CIDetector
进行图片解析,比较简单。- (void)choicePhoto{ //调用相册 UIImagePickerController *imagePicker = [[UIImagePickerController alloc]init]; //UIImagePickerControllerSourceTypePhotoLibrary为相册 imagePicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; //设置代理UIImagePickerControllerDelegate和UINavigationControllerDelegate imagePicker.delegate = self; [self presentViewController:imagePicker animated:YES completion:nil]; } //选中图片的回调 -(void)imagePickerController:(UIImagePickerController*)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { //取出选中的图片 UIImage *pickImage = info[UIImagePickerControllerOriginalImage]; NSData *imageData = UIImagePNGRepresentation(pickImage); CIImage *ciImage = [CIImage imageWithData:imageData]; //创建探测器 //CIDetectorTypeQRCode表示二维码,这里选择CIDetectorAccuracyLow识别速度快 CIDetector *detector = [CIDetector detectorOfType:CIDetectorTypeQRCode context:nil options:@{CIDetectorAccuracy: CIDetectorAccuracyLow}]; NSArray *feature = [detector featuresInImage:ciImage]; //取出探测到的数据 for (CIQRCodeFeature *result in feature) { NSString *content = result.messageString;// 这个就是我们想要的值 } [self dismissViewControllerAnimated:YES completion:nil]; }
常用的二维码或条形码就是这些吧,后续再进行补充。
作者:褪色的旧衬衫
链接:http://www.jianshu.com/p/45ef464c0c8d
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
iOS原生实现二维码扫描
最新推荐文章于 2020-07-01 19:06:08 发布