现在的app多少都会加入二维码扫描功能,方便快捷,开发中常常会碰到这样的需求.
定义会话和输出流对象
@property (nonatomic) AVCaptureSession *captureSession;
@property (nonatomic) AVCaptureVideoPreviewLayer *videoPreviewLayer;
// 获取 AVCaptureDevice 实例
NSError * error;
AVCaptureDevice *captureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
// 初始化输入流
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:captureDevice error:&error];
if (!input) {
NSLog(@"%@", [error localizedDescription]);
return NO;
}
// 创建会话
_captureSession = [[AVCaptureSession alloc] init];
// 添加输入流
[_captureSession addInput:input];
// 初始化输出流
AVCaptureMetadataOutput *captureMetadataOutput = [[AVCaptureMetadataOutput alloc] init];
// 添加输出流
[_captureSession addOutput:captureMetadataOutput];
// 创建dispatch queue.
dispatch_queue_t dispatchQueue;
dispatchQueue = dispatch_queue_create(kScanQRCodeQueueName, NULL);
[captureMetadataOutput setMetadataObjectsDelegate:self queue:dispatchQueue];
// 设置数据类型 AVMetadataObjectTypeQRCode
[captureMetadataOutput setMetadataObjectTypes:[NSArray arrayWithObject:AVMetadataObjectTypeQRCode]];
// 创建输出对象
_videoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:_captureSession];
[_videoPreviewLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];
[_videoPreviewLayer setFrame:self.view.bounds];
[self.view.layer addSublayer:_videoPreviewLayer];
// 开始会话
[_captureSession startRunning];
这里准备工作就做完了,在startRunning后就会开启二维码的扫描,然后遵守AVCaptureMetadataOutputObjectsDelegate
代理 ,在代理方法中处理扫码获得的数据
-(void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects
fromConnection:(AVCaptureConnection *)connection
{
if (metadataObjects != nil && [metadataObjects count] > 0) {
AVMetadataMachineReadableCodeObject *metadataObj = [metadataObjects objectAtIndex:0];
NSString *result;
[_captureSession stopRunning];
if ([[metadataObj type] isEqualToString:AVMetadataObjectTypeQRCode]) {
result = metadataObj.stringValue;
} else {
NSLog(@"不是二维码");
}
}
}
这样最基础的二维码扫描就做完了,但是用户体验并不好,因为默认的是全屏扫描,而且原生的二维码扫描很快,容易误操作,影响用户体验.
所以我们需要限定扫描的范围,AVCaptureMetadataOutput 有对应的属性设置扫描范围,但是它的坐标系有些特殊 并不是常见的(x,y,width,height)而是(y,x,height,width),所以就需要将坐标翻转过来,下面是一个左右间距各50,居中的正方形扫描框,那么就将扫描范围限定在这个扫描框里。
AVCaptureMetadataOutput *captureMetadataOutput = [[AVCaptureMetadataOutput alloc] init];
[captureMetadataOutput setRectOfInterest:CGRectMake(([UIScreen mainScreen].bounds.size.height/2-([UIScreen mainScreen].bounds.size.width-100)/2)/
[UIScreen mainScreen].bounds.size.height,50/[UIScreen mainScreen].bounds.size.width, ([UIScreen mainScreen].bounds.size.width-100)/[UIScreen mainScreen].bounds.size.height, ([UIScreen mainScreen].bounds.size.width-100)/[UIScreen mainScreen].bounds.size.width)];
最后嘛 可以利用view的动画来完成扫描框的线性”扫描”,提升用户体验
下面是利用masory约束的一个 左右间距50并居中的扫描框,然后利用动画完成线性的扫描,欺骗用户…….0.0
UIView * anniView= [[UIView alloc]init];
anniView.tag = 111;
[self.view addSubview:anniView];
[anniView mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.equalTo(self.view.mas_centerX);
make.centerY.equalTo(self.view.mas_centerY);
make.left.equalTo(self.view).with.offset(50);
make.right.equalTo(self.view).with.offset(-50);
make.width.equalTo(anniView.mas_height);
}];
//扫描框
UIImageView * imageView = [[UIImageView alloc]init];
[anniView addSubview:imageView];
[imageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(anniView).insets(UIEdgeInsetsMake(0, 0, 0, 0));
}];
imageView.image = [UIImage imageNamed:@"cornround"];
//扫描线
UIImageView * line = [[UIImageView alloc]init];
[anniView insertSubview:line aboveSubview:imageView];
line.backgroundColor = [UIColor clearColor];
line.image = [UIImage imageNamed:@"line"];
[line mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(anniView).with.offset(0);
make.right.equalTo(anniView).with.offset(0);
make.top.equalTo(anniView).with.offset(0);
make.height.equalTo(@2);
}];
CABasicAnimation *animationMove = [CABasicAnimation animationWithKeyPath:@"transform.translation.y"];
[animationMove setFromValue:@(0)];
[animationMove setToValue:@([UIScreen mainScreen].bounds.size.width-102)];
animationMove.duration = 5.0f;
animationMove.delegate = self;
animationMove.repeatCount = OPEN_MAX;
animationMove.removedOnCompletion = NO;
animationMove.fillMode = kCAFillModeForwards;
animationMove.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[line.layer addAnimation:animationMove forKey:animationKey];
最后在扫码获取到数据之后,停止扫描.并根据tag值获取UIImageView 移除动画,并把扫描框从当前视图中移除