相信很多小伙伴都做過二維碼的掃描生成,網(wǎng)上也有很多相關的Demo和博客從一開始的三方架構到后來用原生的都有,我之前寫過一篇關于二維碼的博客,不過之前使用swift寫的,可能現(xiàn)在已經不能看了(沒辦法,還要轉...)今天寫OC版的,著重說一下二維碼的啟動卡頓問題,之前一直沒有優(yōu)化,話不多說,先看效果圖吧
這個是仿照微信的啟動效果,不會卡頓,十分流暢,之前的一般是卡一下才會啟動的,現(xiàn)在是把加載過程帶入到下個界面,這樣就不會感覺到絲毫的卡頓,下面會給大家著重說這點的
好,我們開始簡單的說下掃描與生成的代碼吧,因為這個已經很多了,所以我這里就簡單的說下吧
1.二維碼生成
使用系統(tǒng)的就十分簡單,幾句代碼就搞定了,效果也十分可觀
首先使用CIFilter濾鏡生成CIImage,可以添加顏色濾鏡自定義背景顏色和二維碼顏色
/**
7.生成CIImage
- parameter size: 大小
- parameter color: 顏色
- parameter bgColor: 背景顏色
- returns: CIImage
*/
-(CIImage*)generateCIImageWithSize:(CGFloat)size color:(UIColor*)color bgColor:(UIColor*)bgColor
{
//設置缺省值
CGFloat QRCodeSize = 300;//默認300
UIColor* QRCodeColor = [UIColor blackColor];//默認黑色二維碼
UIColor * QRCodeBgColor = [UIColor whiteColor];//默認白色背景
//2.二維碼濾鏡
NSData* contentData = [self dataUsingEncoding:NSUTF8StringEncoding];
CIFilter *fileter = [CIFilter filterWithName:@"CIQRCodeGenerator"];
[fileter setValue:contentData forKey:@"inputMessage"];
[fileter setValue:@"H" forKey:@"inputCorrectionLevel"];
CIImage *ciImage = fileter.outputImage;
//3.顏色濾鏡
CIFilter *colorFilter = [CIFilter filterWithName:@"CIFalseColor"];
[colorFilter setValue:ciImage forKey:@"inputImage"];
[colorFilter setValue:[CIColor colorWithCGColor:QRCodeColor.CGColor] forKey:@"inputColor0"];// 二維碼顏色
[colorFilter setValue:[CIColor colorWithCGColor:QRCodeBgColor.CGColor] forKey:@"inputColor1"];// 背景色
//4.生成處理
CIImage*outImage = colorFilter.outputImage;
CGFloat scale = QRCodeSize / outImage.extent.size.width;
return [colorFilter.outputImage imageByApplyingTransform:CGAffineTransformMakeScale(scale, scale)];
}
然后就可以直接使用CIImage生成二維碼圖片,我們這里繪制了一下logo(仿微信)
/**
6.生成二維碼
- parameter size: 大小
- parameter color: 顏色
- parameter bgColor: 背景顏色
- parameter logo: 圖標
- parameter radius: 圓角
- parameter borderLineWidth: 線寬
- parameter borderLineColor: 線顏色
- parameter boderWidth: 帶寬
- parameter borderColor: 帶顏色
- returns: 自定義二維碼
*/
-(UIImage*)generateQRCodeWithSize:(CGFloat)size
color:(UIColor*)color
bgColor:(UIColor*)bgColor
logo:(UIImage*)logo
radius:(CGFloat)radius
borderLineWidth:(CGFloat)borderLineWidth
borderLineColor:(UIColor*)borderLineColor
boderWidth:(CGFloat)boderWidth
borderColor:(UIColor*)borderColor
{
CIImage* ciImage = [self generateCIImageWithSize:size color:color bgColor:bgColor];
UIImage *image = [UIImage imageWithCIImage:ciImage];
if (!logo) return image;
if (!image) return nil;
CGFloat logoWidth = image.size.width/4;
CGRect logoFrame = CGRectMake((image.size.width - logoWidth) / 2,(image.size.width - logoWidth) / 2,logoWidth,logoWidth);
// 繪制logo
UIGraphicsBeginImageContextWithOptions(image.size, false, [UIScreen mainScreen].scale);
[image drawInRect:CGRectMake(0, 0, image.size.width, image.size.height)];
//線框
UIImage*logoBorderLineImagae = [logo getRoundRectImageWithSize:logoWidth radius:radius borderWidth:borderLineWidth borderColor:borderLineColor];
//邊框
UIImage*logoBorderImagae = [logoBorderLineImagae getRoundRectImageWithSize:logoWidth radius:radius borderWidth:boderWidth borderColor:borderColor];
[logoBorderImagae drawInRect:logoFrame];
UIImage* QRCodeImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return QRCodeImage;
}
上面的代碼寫的比較多,主要是加了很多自定義的設置參數(shù),所以比較繁瑣,不過大家如果使用的話很簡單,我給出了很多api一些參數(shù)都已經使用default了,如果你想要微信的效果只要調用方法3傳入一個logo就行
/**
1.生成二維碼
- returns: 黑白普通二維碼(大小為300)
*/
-(UIImage*)generateQRCode;
/**
2.生成二維碼
- parameter size: 大小
- returns: 生成帶大小參數(shù)的黑白普通二維碼
*/
-(UIImage*)generateQRCodeWithSize:(CGFloat)size;
/**
3.生成二維碼
- parameter logo: 圖標
- returns: 生成帶Logo二維碼(大小:300)
*/
-(UIImage*)generateQRCodeWithLogo:(UIImage*)logo;
/**
4.生成二維碼
- parameter size: 大小
- parameter logo: 圖標
- returns: 生成大小和Logo的二維碼
*/
-(UIImage*)generateQRCodeWithSize:(CGFloat)size
logo:(UIImage*)logo;
/**
5.生成二維碼
- parameter size: 大小
- parameter color: 顏色
- parameter bgColor: 背景顏色
- parameter logo: 圖標
- returns: 帶Logo拆又、顏色二維碼
*/
-(UIImage*)generateQRCodeWithSize:(CGFloat)size
color:(UIColor*)color
bgColor:(UIColor*)bgColor
logo:(UIImage*)logo;
/**
6.生成二維碼
- parameter size: 大小
- parameter color: 顏色
- parameter bgColor: 背景顏色
- parameter logo: 圖標
- parameter radius: 圓角
- parameter borderLineWidth: 線寬
- parameter borderLineColor: 線顏色
- parameter boderWidth: 帶寬
- parameter borderColor: 帶顏色
- returns: 自定義二維碼
*/
-(UIImage*)generateQRCodeWithSize:(CGFloat)size
color:(UIColor*)color
bgColor:(UIColor*)bgColor
logo:(UIImage*)logo
radius:(CGFloat)radius
borderLineWidth:(CGFloat)borderLineWidth
borderLineColor:(UIColor*)borderLineColor
boderWidth:(CGFloat)boderWidth
borderColor:(UIColor*)borderColor;
另外二維碼的生成是比較耗時的有可能會阻塞主線程,特別是配置比較低的設配會有點卡頓,有經驗的同學都知道像這種耗時(特別是繪制logo的二維碼的時候)的操作要放到子線程去的
dispatch_async(dispatch_get_global_queue(0, 0), ^{
UIImage *image = [@"大家好,我是炮炮兵!" generateQRCodeWithLogo:_headImageView.image];
dispatch_async(dispatch_get_main_queue(), ^{
_QRCodeImageView.image = image;
});
});
2.二維碼識別
識別圖片中的二維碼,也很簡單,幾句代碼
-(NSString*)scanCodeContent
{
NSData *imageData = UIImagePNGRepresentation(self);
CIImage *ciImage = [CIImage imageWithData:imageData];
CIContext *context = [CIContext contextWithOptions:@{kCIContextUseSoftwareRenderer : @(false), kCIContextPriorityRequestLow : @(false)}];
//創(chuàng)建探測器
CIDetector *detector = [CIDetector detectorOfType:CIDetectorTypeQRCode context:context options:@{CIDetectorAccuracy: CIDetectorAccuracyHigh}];
NSArray *features = [detector featuresInImage:ciImage];
CIQRCodeFeature *feature = [features firstObject];
return feature.messageString.length ? feature.messageString : @"未識別!";
}
這里說一下,當圖片中有2個二維碼時也是可以掃描出來的,他返回的是一個數(shù)組,如果有2個掃描結果就會返回2個的結果放到數(shù)組中,不過一般的需求都是顯示一個結果,所以很多博客中都是直接取了firstObject的(當然我這個也是,嘿嘿,有需求或者想試一下的小伙伴可以自行打個斷點找一個多二維碼的圖片試一下)
3.二維碼掃描
這里就貼一下代碼,輸入輸出會話什么的都已經寫爛了,我這里就不在說了
//初始化掃描二維碼
-(void)initScanCode
{
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
NSError *error = nil;
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
if (error)
{
[self showMessage:@"攝像頭不可用" title:@"溫馨提示" andler:nil];
return;
}
if ([device lockForConfiguration:nil])
{
//自動白平衡
if ([device isWhiteBalanceModeSupported:AVCaptureWhiteBalanceModeContinuousAutoWhiteBalance])
{
[device setWhiteBalanceMode:AVCaptureWhiteBalanceModeContinuousAutoWhiteBalance];
}
//自動對焦
if ([device isFocusModeSupported:AVCaptureFocusModeContinuousAutoFocus])
{
[device setFocusMode:AVCaptureFocusModeContinuousAutoFocus];
}
//自動曝光
if ([device isExposureModeSupported:AVCaptureExposureModeContinuousAutoExposure])
{
[device setExposureMode:AVCaptureExposureModeContinuousAutoExposure];
}
[device unlockForConfiguration];
}
self.captureMetadataOutput = [[AVCaptureMetadataOutput alloc] init];
[self.captureMetadataOutput setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
self.captureSession = [[AVCaptureSession alloc] init];
[self.captureSession setSessionPreset:AVCaptureSessionPresetHigh];
[self.self.captureSession canAddInput:input] ? [self.captureSession addInput:input] : nil;
[self.captureSession canAddOutput:self.captureMetadataOutput] ? [self.captureSession addOutput:self.captureMetadataOutput] : nil;
[self.captureMetadataOutput setMetadataObjectTypes:@[
AVMetadataObjectTypeQRCode,
AVMetadataObjectTypeCode39Code,
AVMetadataObjectTypeCode128Code,
AVMetadataObjectTypeCode39Mod43Code,
AVMetadataObjectTypeEAN13Code,
AVMetadataObjectTypeEAN8Code,
AVMetadataObjectTypeCode93Code
]];
self.captureVideoPreviewLayer = [AVCaptureVideoPreviewLayer layerWithSession:self.captureSession];
self.captureVideoPreviewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
self.captureVideoPreviewLayer.frame = self.view.layer.bounds;
[self.view.layer insertSublayer:self.captureVideoPreviewLayer atIndex:0];
[self loadScan];
}
這里二維碼相關的設置設置完以后我們就要啟動掃描了,這里一般是直接調方法 [self.captureSession startRunning]來開始掃描,但問題就在這里,這個方法是會阻塞主線程的直到啟動完成,api說明里是有注明的,所以我們就不能這樣用,會照成卡頓,我們要放到子線程去啟動就行了,順便做一個加載動畫,跟微信的做法保持一致
//啟動掃描
-(void)loadScan
{
[_loaddingIndicatorView startAnimating ];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self.captureSession startRunning];
dispatch_async(dispatch_get_main_queue(), ^{
[_loaddingIndicatorView stopAnimating];
[UIView animateWithDuration:0.25 animations:^{
_contentLabel.alpha = 1;
_boxLayoutConstraint.constant = [UIScreen mainScreen].bounds.size.width*0.6;
[self.view layoutIfNeeded];
} completion:^(BOOL finished) {
_scanLine.frame = CGRectMake(0 , 0, [UIScreen mainScreen].bounds.size.width*0.6, 3);
[_scanLine.layer addAnimation:[self moveAnimation] forKey:nil];
[self setScanReact:NO];
}];
self.captureMetadataOutput.rectOfInterest = [self.captureVideoPreviewLayer metadataOutputRectOfInterestForRect:_scanPane.frame];
});
});
}
這里啟動完成之后設置掃描設置掃描范圍的要說一下,很多博客和教程都是自己去設置掃描范圍的(那個生成范圍的轉換很坑爹,不好把控),系統(tǒng)提供的有把CGReact轉換成OutputReact的方法:
- (CGRect)metadataOutputRectOfInterestForRect:(CGRect)rectInLayerCoordinates
這里設置的時候要注意要等你的frame穩(wěn)定以后再設置,特別是使用AuotLayout的,不要一上來就設置這個,因為你的frame更新至少要在layoutSubviews那里才會更新的,我一般就放在掃描啟動完成以后才設置范圍
同時開始掃描和停止掃描也是放在子線程中
//開始掃描
- (void)startScan
{
[_scanLine.layer addAnimation:[self moveAnimation] forKey:nil];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self.captureSession startRunning];
});
}
//停止掃描
- (void)stopScan
{
[_scanLine.layer removeAllAnimations];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if (self.captureSession.isRunning )
{
[self.captureSession stopRunning];
}
});
}
好了,就這些了,代碼稍微有點亂,小伙伴們請諒解