关于扫描二维码的一些需求点(扫描二维码,闪光灯,相册,动画)
网上相关的很多,我这里是总结下自己遇到的需求,总共有几个需求点,
- 识别二维码中的数据
- 扫码动画
- 打开/关闭闪光灯
-
取出相册第一张图片,点击可以打开相册,并识别相册二维码
先给个gif是整体效果图,估计图片显示问题看起来有点卡...
打开相机开始扫描
这个需求最重要的就是打开相机扫描吧,首先先确定你的权限,以及在plist加上这些描述
另外需要确认是否打开权限如果没有打开权限直接操作会崩溃的...
AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
if(status == AVAuthorizationStatusAuthorized) {
// authorized
[self setupCamera];
[self setupLatestAsset];
} else {
// alert :@"Tips" message:@"Authorization is required to use the camera, please check your permission settings: Settings> Privacy> Camera"
}
1. 识别二维码中的数据
这里需要导入#import <AVFoundation/AVFoundation.h>
主要会用到的有下面的
@property (strong, nonatomic) AVCaptureDevice *device;
@property (strong, nonatomic) AVCaptureDeviceInput *input;
@property (strong, nonatomic) AVCaptureMetadataOutput *output;
@property (strong, nonatomic) AVCaptureSession *session;
@property (strong, nonatomic) AVCaptureVideoPreviewLayer *preview;
其中AVCaptureDevice是设备,AVCaptureDeviceInput是输入,我的理解就是摄像头的一些设置,AVCaptureMetadataOutput是输出,数据输出,AVCaptureSession我的理解是事务,就是整个流程的事务,AVCaptureVideoPreviewLayer是扫描画面的图层.懒加载如下
#pragma mark - lazyMethod
- (AVCaptureDevice *)device
{
if (!_device) {
_device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
}
return _device;
}
- (AVCaptureDeviceInput *)input
{
if (!_input) {
_input = [AVCaptureDeviceInput deviceInputWithDevice:self.device error:nil];
}
return _input;
}
- (AVCaptureMetadataOutput *)output
{
if (!_output) {
_output = [[AVCaptureMetadataOutput alloc] init];
}
return _output;
}
- (AVCaptureSession *)session
{
if (!_session) {
_session = [[AVCaptureSession alloc] init];
[_session setSessionPreset:AVCaptureSessionPresetHigh];
}
return _session;
}
- (AVCaptureVideoPreviewLayer *)preview
{
if (!_preview) {
_preview = [AVCaptureVideoPreviewLayer layerWithSession:_session];
_preview.videoGravity = AVLayerVideoGravityResizeAspectFill;
_preview.frame =self.view.layer.bounds;
}
return _preview;
}
接下来就是上述属性的设置
// 链接输入输出
if ([self.session canAddInput:self.input]){
[self.session addInput:self.input];
}
// http://stackoverflow.com/questions/31063846/avcapturemetadataoutput-setmetadataobjecttypes-unsupported-type-found
if ([self.session canAddOutput:self.output]){
[self.session addOutput:self.output];
[self.output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
// 设置条码类型
self.output.metadataObjectTypes =@[AVMetadataObjectTypeQRCode];
}
// 添加扫描画面
[self.view.layer insertSublayer:self.preview atIndex:0];
另外需要遵守AVCaptureMetadataOutputObjectsDelegate协议,因为你要在这里拿到扫描出二维码的信息
#pragma mark - AVCaptureMetadataOutputObjectsDelegate
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection
{
NSString *stringValue;
if ([metadataObjects count] > 0){
//停止扫描
[self.session stopRunning];
AVMetadataMachineReadableCodeObject * metadataObject = [metadataObjects objectAtIndex:0];
stringValue = metadataObject.stringValue;
NSLog(@"------------%@-----------",stringValue);
}
}
到了这里其实就已经可以识别二维码了,另外还有三个小需求(动画,相册,闪光灯)
2. 扫码动画
动画这一块我并不想讲的太多,网上其实有很多,除去一些基本的设置外,就是做一个transform.translation.y
的动画,给你的线加上下面这个动画就ok,很多时候都会写个kvo来决定动画开启,这里见仁见智了
- (CABasicAnimation *)moveYTime:(float)time fromY:(NSNumber *)fromY toY:(NSNumber *)toY rep:(int)rep
{
CABasicAnimation *animationMove = [CABasicAnimation animationWithKeyPath:@"transform.translation.y"];
[animationMove setFromValue:fromY];
[animationMove setToValue:toY];
animationMove.duration = time;
animationMove.delegate = self;
animationMove.repeatCount = rep;
animationMove.fillMode = kCAFillModeForwards;
animationMove.removedOnCompletion = NO;
animationMove.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
return animationMove;
}
3. 打开/关闭闪光灯
闪光灯用到的类主要是AVCaptureDevice,还是直接贴方法
// 打开手电筒开关按钮点击事件
- (void)torchOnTouchButton:(UIButton *)sender{
Class captureDeviceClass = NSClassFromString(@"AVCaptureDevice");
if (captureDeviceClass != nil) {
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
// 判断是否有闪光灯
if ([device hasTorch]) {
// 请求独占访问硬件设备
[device lockForConfiguration:nil];
if (sender.tag == 0) {
sender.tag = 1;
[self.torchButton setTitle:@"关闭闪光灯" forState:UIControlStateNormal];
[device setTorchMode:AVCaptureTorchModeOn]; // 手电筒开
}else{
sender.tag = 0;
[self.torchButton setTitle:@"打开闪光灯" forState:UIControlStateNormal];
[device setTorchMode:AVCaptureTorchModeOff]; // 手电筒关
}
// 请求解除独占访问硬件设备
[device unlockForConfiguration];
}
}
}
我这里主要使用button的tag来记录是否打开情况,另外,记得在页面关闭是关了闪光灯
4. 取出相册最新图片,点击打开相册,并识别相册二维码
其实我想记录的原因就是因为最后一个需求,不然上面的其实网上都很多,主要就是去除相册最新的一张照片,网上的方法都是iOS 7,iOS 8的,在iOS 9之后都过期了.
打开相册
打开相册也是需要判断是否给了权限,万万不可不判断强上,另外弹出系统的照片选择器之后
// 打开相册
- (void)openCameralClick:(id)sender {
// 1.判断相册是否可以打开
if (![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary]) return;
// 2. 创建图片选择控制器
UIImagePickerController *ipc = [[UIImagePickerController alloc] init];
// 3.设置type
ipc.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
// 4.设置代理
ipc.delegate = self;
// 5.modal出这个控制器
[self presentViewController:ipc animated:YES completion:nil];
}
打开相册之后,需要实现UIImagePickerControllerDelegate,UINavigationControllerDelegate协议,拿到选择回来的照片.也许你好奇为什么这里代码如此之多,因为扫描从相册中取出的二维码跟直接用相机还不一样,需要使用到CIDetector类,这个类貌似人脸识别什么也用,图片探测器.我也遇到了扫描不出的情况,代码中注释github链接也基本解决了我遇到的问题
#pragma mark - UIImagePickerControllerDelegate
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info
{
// 注意: 如果实现了该方法, 当选中一张图片时系统就不会自动关闭相册控制器
[picker dismissViewControllerAnimated:YES completion:nil];
// 1.取出选中的图片
UIImage *pickImage = (UIImage *)[info objectForKey:UIImagePickerControllerEditedImage];
if (!pickImage){
pickImage = (UIImage *)[info objectForKey:UIImagePickerControllerOriginalImage];
}
// http://stackoverflow.com/questions/10746212/cidetector-and-uiimagepickercontroller
int exifOrientation;
switch (pickImage.imageOrientation) {
case UIImageOrientationUp:
exifOrientation = 1;
break;
case UIImageOrientationDown:
exifOrientation = 3;
break;
case UIImageOrientationLeft:
exifOrientation = 8;
break;
case UIImageOrientationRight:
exifOrientation = 6;
break;
case UIImageOrientationUpMirrored:
exifOrientation = 2;
break;
case UIImageOrientationDownMirrored:
exifOrientation = 4;
break;
case UIImageOrientationLeftMirrored:
exifOrientation = 5;
break;
case UIImageOrientationRightMirrored:
exifOrientation = 7;
break;
default:
break;
}
// 2.从选中的图片中读取二维码数据
// 2.1创建一个探测器
self.detector = [CIDetector detectorOfType:CIDetectorTypeQRCode context:nil options:@{CIDetectorAccuracy: CIDetectorAccuracyHigh}];
// 2.2利用探测器探测数据
CIImage *theCIImage = [[CIImage alloc] initWithImage:pickImage];
// CIImage *theCIImage = [CIImage imageWithCGImage:pickImage.CGImage];
NSArray *features = [self.detector featuresInImage:theCIImage options:@{CIDetectorImageOrientation:[NSNumber numberWithInt:exifOrientation]}];
NSLog(@"theCGImage: %@", pickImage.CGImage);
NSLog(@"theCIImage: %@", theCIImage);
NSLog(@"arr: %@", features);
// 2.3取出探测到的数据
for (CIQRCodeFeature *result in features) {
NSLog(@"%@",result.messageString);
NSString *urlStr = result.messageString;
}
}
最后一个问题是取出用户相册中最新的一张图片展示在扫码界面,这里需要导入#import <Photos/Photos.h>
使用到的类是iOS 9之后的PHAsset直接上代码吧.网上有很多老的代码,我找来都是过期了,所以在此记录
PHFetchOptions *options = [[PHFetchOptions alloc] init];
PHFetchResult *assetsFetchResults = [PHAsset fetchAssetsWithOptions:options];
PHAsset *phasset = [assetsFetchResults lastObject];
PHCachingImageManager *imageManager = [[PHCachingImageManager alloc] init];
[imageManager requestImageForAsset:phasset targetSize:CGSizeMake(300, 300) contentMode:PHImageContentModeAspectFill options:nil resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) {
// 这里的 result 就是用户最新的image
}];