關于掃描二維碼的一些需求點(掃描二維碼,閃光燈,相冊,動畫)
網上相關的很多,我這里是總結下自己遇到的需求,總共有幾個需求點,
- 識別二維碼中的數據
- 掃碼動畫
- 打開/關閉閃光燈
-
取出相冊第一張圖片,點擊可以打開相冊,并識別相冊二維碼
先給個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
}];