对于写代码,我一向是只要时间允许,能自己写的绝对不用第三方的。因为总是用第三方的话,会给工程带来很大的不稳定性和很多坑。嗯,我现在就身在其中。
二维码扫描,这是一个很普遍的功能,在我负责的app里也有,只不过那是个第三方的。乘着刚刚用Swift搭完架子,现在决定尝试对其封装一下,然后应用到工程中。
二维码扫描使用的是AVFoundation框架 ,主要涉及的类有:
- AVCaptureDevice
- AVCaptureDeviceInput
- AVCaptureMetadataOutput
- AVCaptureVideoPreviewLayer
- AVCaptureSession
要说应用,肯定很简单,只不过这里面的坑太多太多了。
简单的流程,总结如下:
- 1.使用方法
[AVCaptureDevice authorizationStatusForMediaType:mediaType];
检查权限,但是这里的type类型只能是AVMediaTypeVideo
或者AVMediaTypeAudio
- 2.使用方法
+ (void)requestAccessForMediaType:(AVMediaType)mediaType completionHandler:(void (^)(BOOL granted))handler
获取权限,这里需要注意的是回调不一定就是在主线程 - 3.创建device对象
- 4.使用device对象创建
AVCaptureDeviceInput
对象 - 5.初始化
AVCaptureMetadataOutput
对象和设置代理 - 6.初始化
AVCaptureSession
任务管理对象 - 7.使用如下代码添加输入输出:
if([session canAddInput:deviceInput]) {
[session addInput:deviceInput];
}
if([session canAddOutput:output]) {
[session addOutput:output];
}
- 8.设置output对象的属性
metadataObjectTypes
值为AVMetadataObjectTypeQRCode
- 9.使用session初始化
AVCaptureVideoPreviewLayer
,该layer就是背景透明的地方 - 10.使用session调用方法
startRunning
启动扫描 - 11.调用layer的
- (CGRect)metadataOutputRectOfInterestForRect:(CGRect)rectInLayerCoordinates
方法获取output的rectOfInterest
,这里的rectInLayerCoordinates
参数就是扫描框的位置,即识别区。注意,这里必须要写到startRunning调用之后。 - 12.调用layer的
rectForMetadataOutputRectOfInterest
方法获取扫描框的位置 - 13.获取结果
以上,这是一个比较好理解和使用的思路,这里的rectOfInterest其实也可以自己计算获取到,但是比较难掌握,因为它和AVCaptureVideoPreviewLayer的videoGravity有关,也和session的sessionPreset有关,具体的待我稍作研究补上。
具体的代码如下:
- (void)configUI {
AVMediaType mediaType = AVMediaTypeVideo;
// 先检查权限,如果没有权限,就去申请获取
AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:mediaType];
switch (status) {
case AVAuthorizationStatusNotDetermined:{
[AVCaptureDevice requestAccessForMediaType:mediaType completionHandler:^(BOOL granted) {
dispatch_async(dispatch_get_main_queue(), ^{
if (granted) {
[self continueConfigUI:mediaType];
} else {
NSLog(@"denied");
}
});
}];
}
break;
case AVAuthorizationStatusAuthorized:{
[self continueConfigUI:mediaType];
}
break;
default:
NSLog(@"denied");
break;
}
}
- (void)continueConfigUI:(AVMediaType)mediaType {
AVCaptureDevice * device = [AVCaptureDevice defaultDeviceWithMediaType:mediaType];
if (!device) {
return;
}
CGRect cameraFrame = CGRectMake(10, 100, 300, 300);
CGRect scanRect = CGRectMake(cameraFrame.size.width / 2 - 100, cameraFrame.size.height / 2 - 100, 200, 200);
AVCaptureDeviceInput * deviceInput = [AVCaptureDeviceInput deviceInputWithDevice:device error:nil];
output = [[AVCaptureMetadataOutput alloc] init];
[output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
// CGFloat top = scanRect.origin.y / CGRectGetHeight(cameraFrame);
// CGFloat left = scanRect.origin.x / CGRectGetWidth(cameraFrame);
// CGFloat width = scanRect.size.width / CGRectGetWidth(cameraFrame);
// CGFloat height = scanRect.size.height / CGRectGetHeight(cameraFrame);
// output.rectOfInterest = CGRectMake(top, left, height, width);
session = [[AVCaptureSession alloc] init];
if([session canAddInput:deviceInput]) {
[session addInput:deviceInput];
}
if([session canAddOutput:output]) {
[session addOutput:output];
}
output.metadataObjectTypes = @[AVMetadataObjectTypeQRCode]; // 必须放在上面设置完session之后
_cameraView = [[UIView alloc] initWithFrame:cameraFrame];
_cameraView.backgroundColor = [UIColor whiteColor];
[self.view addSubview:_cameraView];
// 添加扫描画面
AVCaptureVideoPreviewLayer * layer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:session];
layer.videoGravity = AVLayerVideoGravityResizeAspectFill; // 该值会影响展示
layer.frame = _cameraView.bounds;
[_cameraView.layer addSublayer:layer];
// output.rectOfInterest = [layer metadataOutputRectOfInterestForRect:scanRect];
[session startRunning];
output.rectOfInterest = [layer metadataOutputRectOfInterestForRect:scanRect]; // 采用此方法必须放到startRunning之后
[self.view bringSubviewToFront:_backButton];
[self.view bringSubviewToFront:_startButton];
_middleView = [[UIView alloc] initWithFrame:[layer rectForMetadataOutputRectOfInterest:output.rectOfInterest]];
_middleView.backgroundColor = [UIColor clearColor];
_middleView.layer.borderWidth = 5;
_middleView.layer.borderColor = [UIColor orangeColor].CGColor;
[_cameraView addSubview:_middleView];
// UIView * recView = [[UIView alloc] initWithFrame:scanRect];
// recView.backgroundColor = [UIColor clearColor];
// recView.layer.borderWidth = 2;
// recView.layer.borderColor = [UIColor greenColor].CGColor;
// [_cameraView addSubview:recView];
}
- (void)captureOutput:(AVCaptureOutput *)output didOutputMetadataObjects:(NSArray<__kindof AVMetadataObject *> *)metadataObjects fromConnection:(AVCaptureConnection *)connection {
if(metadataObjects.count > 0) {
[session stopRunning];
AVMetadataMachineReadableCodeObject * readCode = metadataObjects.firstObject;
UIAlertController * alertVC = [UIAlertController alertControllerWithTitle:@"结果" message:readCode.stringValue preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction * alertAction = [UIAlertAction actionWithTitle:@"好的" style:UIAlertActionStyleDefault handler:nil];
[alertVC addAction:alertAction];
[self presentViewController:alertVC animated:YES completion:nil];
NSLog(@"%@", readCode.stringValue);
}
}
网友评论