iOS二维码扫描与生成(优化启动卡顿)
相信很多小伙伴都做过二维码的扫描生成,网上也有很多相关的Demo和博客从一开始的三方架构到后来用原生的都有,我之前写过一篇关于二维码的博客,不过之前使用swift写的,可能现在已经不能看了(没办法,还要转...)今天写OC版的,着重说一下二维码的启动卡顿问题,之前一直没有优化,话不多说,先看效果图吧
Untitled.gif这个是仿照微信的启动效果,不会卡顿,十分流畅,之前的一般是卡一下才会启动的,现在是把加载过程带入到下个界面,这样就不会感觉到丝毫的卡顿,下面会给大家着重说这点的
好,我们开始简单的说下扫描与生成的代码吧,因为这个已经很多了,所以我这里就简单的说下吧
1.二维码生成
使用系统的就十分简单,几句代码就搞定了,效果也十分可观
首先使用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;
}
上面的代码写的比较多,主要是加了很多自定义的设置参数,所以比较繁琐,不过大家如果使用的话很简单,我给出了很多api一些参数都已经使用default了,如果你想要微信的效果只要调用方法3传入一个logo就行
/**
1.生成二维码
- returns: 黑白普通二维码(大小为300)
*/
-(UIImage*)generateQRCode;
/**
2.生成二维码
- parameter size: 大小
- returns: 生成带大小参数的黑白普通二维码
*/
-(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)}];
//创建探测器
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个二维码时也是可以扫描出来的,他返回的是一个数组,如果有2个扫描结果就会返回2个的结果放到数组中,不过一般的需求都是显示一个结果,所以很多博客中都是直接取了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];
});
});
}
这里启动完成之后设置扫描设置扫描范围的要说一下,很多博客和教程都是自己去设置扫描范围的(那个生成范围的转换很坑爹,不好把控),系统提供的有把CGReact转换成OutputReact的方法:
- (CGRect)metadataOutputRectOfInterestForRect:(CGRect)rectInLayerCoordinates
这里设置的时候要注意要等你的frame稳定以后再设置,特别是使用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];
}
});
}
好了,就这些了,代码稍微有点乱,小伙伴们请谅解