iOS:二维码
Page content
传统互联网的入口在浏览器,移动互联网的入口在二维码
前言
在iOS7.0前,实现二维码扫描还需要借助第三方库,但如今,苹果的API逐渐完善与强大,这一功能我们可以借助AVFoundation框架来搞定。
生成
绘制logo
~~~objective-c
/**
添加二维码中间logo图片
- parameter centerImg: logo图片
- parameter size: logo图片像素宽高
- parameter bgImage: 二维码图片
- returns: 合成后图片
*/
+ (UIImage *)drawCenterImage:(UIImage *)centerImage BgImage:(UIImage *)bgImage Size:(CGFloat)size {
// 1. 开启一个图形上下文
CGSize bgSize = bgImage.size;
UIGraphicsBeginImageContextWithOptions(CGSizeMake(bitmapImgSize, bitmapImgSize), false, ScreenScale);
// 2. 绘制大图片
[bgImage drawInRect:CGRectMake(0, 0, bgSize.width, bgSize.height)];
// 3. 在中间绘制小图片
CGFloat x = (bgSize.width - size)/2.f;
CGFloat y = (bgSize.height - size)/2.f;
[centerImage drawInRect:CGRectMake(x, y, size, size)];
// 4. 获取最终合成图片
UIImage * image = UIGraphicsGetImageFromCurrentImageContext();
// 5. 关闭上下文
UIGraphicsEndImageContext();
return image;
}
~~~
Bitmap绘图
~~~objective-c
/**
使用位图根据CIImage生成指定大小的高清UIImage
- parameter ciImage: 指定CIImage
- parameter size: size 指定大小
- returns: 生成好的高清图片
*/
+ (UIImage *)bitmapScaleImage:(CIImage *)orieignimage Size:(CGFloat)size {
CGRect extent = orieignimage.extent;
CGFloat scale = MIN(size/extent.size.width, size/extent.size.height);
// 1.创建bitmap;
CGFloat wh = extent.size.width * scale;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray();
CGContextRef bitmapRef = CGBitmapContextCreate(nil, (int)wh, (int)wh, 8, 0, colorSpace, 0);
// 2.图形上下文
CIContext * context = [[CIContext alloc] initWithOptions:0];
CGImageRef bitmapImage = [context createCGImage:orieignimage fromRect:extent];
CGContextSetInterpolationQuality(bitmapRef, kCGInterpolationNone);
CGContextScaleCTM(bitmapRef, scale, scale);
CGContextDrawImage(bitmapRef, extent, bitmapImage);
// 3.保存bitmap到图片
CGImageRef scaledImage = CGBitmapContextCreateImage(bitmapRef);
UIImage * image = [UIImage imageWithCGImage:scaledImage];
// 4.释放内存
CGColorSpaceRelease(colorSpace);
CGContextRelease(bitmapRef);
CGImageRelease(scaledImage);
return image;
}
~~~
二维码绘制
~~~objective-c
/**
接口:根据信息文本生成二维码
- parameter codeInfo: 信息文本
- parameter centerImage: logo图片
- returns: 绘制好的UIImage图片
*/
+ (UIImage *)QRCodeGenerateWithCodeInfo:(NSString *)codeInfo CenterImage:(UIImage *)centerImage {
NSData * codeData = [codeInfo dataUsingEncoding:NSUTF8StringEncoding];
// 1. 创建一个滤镜
CIFilter * filter = [CIFilter filterWithName:@"CIQRCodeGenerator"];
// 1.1 恢复滤镜默认设置
[filter setDefaults];
// 2. 设置二维码滤镜的输入数据, 需要通过KVC, 设置 inputMessage字段, 值为NSData
[filter setValue:codeData forKey:@"inputMessage"];
// 2.1 设置二维码纠错率, 需要通过KVC, 设置 inputCorrectionLevel字段, 值为L, M, Q, H
[filter setValue:@"H" forKey:@"inputCorrectionLevel"];
// 3. 获取输出图片(默认大小是23 X 23)
CIImage * outputImage = [filter outputImage];
// 3.1 后期处理, 借助位图的方式, 放大图片
UIImage * image = [self bitmapScaleImage:outputImage Size:bitmapImgSize];
if (centerImage) {
return [self drawCenterImage:centerImage BgImage:image Size:centerImgSize];
}
return image;
}
~~~
识别
绘制边框
~~~objective-c
/**
* 绘制识别出的二维码边框
- parameter qrFeature: CIQRCodeFeature特征对象
- parameter image: 绘制识别的Image
- returns: 绘制完成的Image
*/
+ (UIImage *)drawQRCodeBoder:(CIQRCodeFeature *)qrcodeFeature Image:(UIImage *)image {
// 1. 开启图形上下文
UIGraphicsBeginImageContext(image.size);
// 2. 绘制图片
[image drawInRect:CGRectMake(0, 0, image.size.width, image.size.height)];
// 3. 上下颠倒坐标系
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextScaleCTM(context, 1.0, -1.0);
CGContextTranslateCTM(context, 0, -image.size.height);
// 4. 绘制识别的二维码的矩形框
UIBezierPath * bezierPath = [UIBezierPath bezierPathWithRect:qrcodeFeature.bounds];
[[UIColor redColor] set];
bezierPath.lineWidth = 3.f;
[bezierPath stroke];
// 5. 取出图片
UIImage * handleImage = UIGraphicsGetImageFromCurrentImageContext();
// 6. 关闭图形上下文
UIGraphicsEndImageContext();
return handleImage;
}
~~~
探测结果
~~~objective-c
/**
接口: 根据图片进行二维码识别,识别成功进行闭包回调
- parameter Image: 要识别的包含二维码的图片
- parameter DrawQRCodeFrame: 是否绘制边框
- parameter result: 闭包回调
*/
+ (void)detectorWithImage:(UIImage *)image DrawQRCodeFrame:(BOOL)drawQRCodeFrame Result:(void (^)(UIImage * image, NSArray * results))result {
// 1. 创建一个二维码探测器
CIContext * context = [[CIContext alloc] init];
CIDetector * detector = [CIDetector detectorOfType:CIDetectorTypeQRCode context:context options:@{CIDetectorAccuracy : CIDetectorAccuracyHigh}];
// 2. 识别图片特征
CIImage * ciImage = [CIImage imageWithCGImage:image.CGImage];
NSArray * features = [detector featuresInImage:ciImage];
NSMutableArray * temps = [NSMutableArray array];
UIImage *tempImage = image;
for (CIFeature * feature in features) {
if ([feature isKindOfClass:[CIQRCodeFeature class]]) {
CIQRCodeFeature * tempFeature = (CIQRCodeFeature *)feature;
[temps addObject:tempFeature.messageString];
if (drawQRCodeFrame) {
tempImage = [self drawQRCodeBoder:tempFeature Image:tempImage];
}
}
if ([feature isKindOfClass:[CITextFeature class]]) {
CITextFeature * tempFeature = (CITextFeature *)feature;
NSLog(@"%@", tempFeature.subFeatures);
}
}
// 3. 音效播放
NSString * audioPath = [[NSBundle mainBundle] pathForResource:@"qrcode_scan_suc.wav" ofType:@""];
NSData * audioData = [NSData dataWithContentsOfFile:audioPath];
AVAudioPlayer * audioPlayer = [[AVAudioPlayer alloc] initWithData:audioData error:nil];
[audioPlayer play];
// 4. 回调
result (tempImage, temps);
}
~~~
扫描
视图
接口:
~~~objective-c
#import <UIKit/UIKit.h>
#define KSW [UIScreen mainScreen].bounds.size.width
#define KSH [UIScreen mainScreen].bounds.size.height
#define VWH 220.f
@interface QRCodeScanView : UIView
/**
* 开启扫描动作
*/
- (void)beignScanAnimation;
/**
* 关闭扫描动作
*/
- (void)endScanAnimation;
@end
~~~
类别:
~~~objective-c
#import "QRCodeScanView.h"
#import "UIImage+Extensions.h"
@interface QRCodeScanView ()
/// 区域边框
@property (nonatomic, strong) UIImageView * frameImgView;
/// 扫描线
@property (nonatomic, strong) UIImageView * lineImgView;
@end
@implementation QRCodeScanView
~~~
属性:
~~~objective-c
#pragma mark - Lazy Loading
- (UIImageView *)frameImgView {
if (!_frameImgView) {
_frameImgView = [[UIImageView alloc] init];
_frameImgView.frame = self.bounds;
NSString *path = [[NSBundle bundleForClass:self.class] pathForResource:@"qrcode_border" ofType:@"png"];
UIImage * frameImg = [UIImage imageWithContentsOfFile:path];
frameImg = [frameImg imageWithColor:UIColorFromRGBx(Theme.sharedInstance.navigationBarBackgroundColor)];
frameImg = [frameImg stretchableImageWithLeftCapWidth:(NSInteger)frameImg.size.width * 0.5f topCapHeight:(NSInteger)frameImg.size.height * 0.5f];
_frameImgView.image = frameImg;
}
return _frameImgView;
}
- (UIImageView *)lineImgView {
if (!_lineImgView) {
_lineImgView = [[UIImageView alloc] init];
_lineImgView.frame = self.bounds;
NSString *path = [[[NSBundle bundleForClass:self.class] resourcePath] stringByAppendingPathComponent:@"qrcode_scanline.png"];
UIImage *lineImage = [UIImage imageWithContentsOfFile:path];
lineImage = [lineImage imageWithColor:UIColorFromRGBx(Theme.sharedInstance.navigationBarBackgroundColor)];
_lineImgView.image = lineImage;
[_lineImgView sizeToFit];
}
return _lineImgView;
}
~~~
初始化:
~~~objective-c
#pragma mark - Init
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
[self addSubview:self.frameImgView];
[self addSubview:self.lineImgView];
self.lineImgView.frame = CGRectMake(0, -self.lineImgView.frame.size.height, self.frame.size.width, self.lineImgView.frame.size.height);
self.clipsToBounds = true;
}
return self;
}
- (void)didMoveToSuperview {
CGRect rect = self.frame;
UIColor * fillColor = [UIColor colorWithRed:1.f/255.f green:1.f/255.f blue:1.f/255.f alpha:1.f];
CAShapeLayer* layerTop = [[CAShapeLayer alloc] init];
layerTop.fillColor = fillColor.CGColor;
layerTop.opacity = 0.5;
layerTop.path = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, KSW, rect.origin.y)].CGPath;
[self.superview.layer addSublayer:layerTop];
CAShapeLayer* layerLeft = [[CAShapeLayer alloc] init];
layerLeft.fillColor = fillColor.CGColor;
layerLeft.opacity = 0.5;
layerLeft.path = [UIBezierPath bezierPathWithRect:CGRectMake(0, rect.origin.y, (KSW-VWH)/2.0f, KSH)].CGPath;
[self.superview.layer addSublayer:layerLeft];
CAShapeLayer* layerRight = [[CAShapeLayer alloc] init];
layerRight.fillColor = fillColor.CGColor;
layerRight.opacity = 0.5;
layerRight.path = [UIBezierPath bezierPathWithRect:CGRectMake(KSW - (KSW-VWH)/2.0f, rect.origin.y, (KSW-VWH)/2.0f, KSH)].CGPath;
[self.superview.layer addSublayer:layerRight];
CAShapeLayer* layerBottom = [[CAShapeLayer alloc] init];
layerBottom.fillColor = fillColor.CGColor;
layerBottom.opacity = 0.5;
layerBottom.path = [UIBezierPath bezierPathWithRect:CGRectMake((KSW-VWH)/2.0f, rect.origin.y + rect.size.height, rect.size.width, KSH - rect.origin.y - rect.size.height)].CGPath;
[self.superview.layer addSublayer:layerBottom];
}
~~~
接口实现:
~~~objective-c
#pragma mark - Public
/**
* 开启扫描动作
*/
- (void)beignScanAnimation {
[UIView animateWithDuration:1.0 animations:^{
[UIView setAnimationRepeatCount:MAXFLOAT];
self.lineImgView.frame = CGRectMake(0, 2 * self.frame.size.height, self.frame.size.width, self.lineImgView.frame.size.height);
}];
}
/**
* 关闭扫描动作
*/
- (void)endScanAnimation {
[self.lineImgView.layer removeAllAnimations];
self.lineImgView.frame = CGRectMake(0, -self.lineImgView.frame.size.height, self.frame.size.width, self.lineImgView.frame.size.height);
}
~~~
用到的UIImage的类扩展方法:
~~~objective-c
/**
* 重新绘制图片
*
* @param color 填充色
* @return UIImage
*/
- (UIImage *)imageWithColor:(UIColor *)color {
UIGraphicsBeginImageContextWithOptions(self.size, NO, self.scale);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(context, 0, self.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextSetBlendMode(context, kCGBlendModeNormal);
CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);
CGContextClipToMask(context, rect, self.CGImage);
[color setFill];
CGContextFillRect(context, rect);
UIImage*newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
// 获取纯色img
+ (UIImage *)imageWithColor:(UIColor *)color {
return [UIImage imageWithColor:color size:(CGSize){1.0f, 1.0f}];
}
+ (UIImage *)imageWithColor:(UIColor *)color size:(CGSize)size {
CGRect rect = CGRectZero;
rect.size = size;
UIGraphicsBeginImageContext(rect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, [color CGColor]);
CGContextFillRect(context, rect);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
~~~
控制器
匿名类别:
~~~objective-c
#import "QRCodeScanViewController.h"
#import "QRCodeScanView.h"
#import "QRCodeScanDevice.h"
#import "QRCodeTool.h"
#import <MobileCoreServices/MobileCoreServices.h>
@interface QRCodeScanViewController ()
<UIImagePickerControllerDelegate,
UINavigationControllerDelegate>
@property (nonatomic, strong) QRCodeScanView * scanView;
@property (nonatomic, strong) QRCodeScanDevice * scanDevice;
@property (nonatomic, strong) UILabel * alertLabel;
/// 相册Controller
@property (nonatomic, strong) UIImagePickerController * imgPLController;
@end
~~~
属性懒加载:
~~~objective-c
#pragma mark - Lazy Loading
- (QRCodeScanView *)scanView {
if (!_scanView) {
CGRect scanViewFrame = CGRectMake((KSW-VWH)/2.0f, (KSH-VWH)/2.0f-kNavigationBarHeight, VWH, VWH);
_scanView = [[QRCodeScanView alloc] initWithFrame:scanViewFrame];
}
return _scanView;
}
- (QRCodeScanDevice *)scanDevice {
if (!_scanDevice) {
_scanDevice = [[QRCodeScanDevice alloc] init];
}
return _scanDevice;
}
- (UILabel *)alertLabel {
if (!_alertLabel) {
_alertLabel = [[UILabel alloc] init];
_alertLabel.font = [UIFont systemFontOfSize:14.f];
_alertLabel.textColor = [UIColor whiteColor];
_alertLabel.text = @"将取景框对准二维码,即可自动扫描";
[_alertLabel sizeToFit];
_alertLabel.center = CGPointMake(KSW/2.0f, self.scanView.frame.origin.y - _alertLabel.bounds.size.height - 10);
}
return _alertLabel;
}
- (UIImagePickerController *)imgPLController {
if (!_imgPLController) {
_imgPLController = [[UIImagePickerController alloc] init];
_imgPLController.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
_imgPLController.allowsEditing = false;
_imgPLController.mediaTypes = @[(NSString *)kUTTypeImage];
_imgPLController.delegate = self;
NSDictionary * attributes = @{NSForegroundColorAttributeName: Theme.sharedInstance.navigationBarTitleColor,
NSFontAttributeName : [UIFont fontWithName:@"PingFang-SC-Regular" size:18]};
_imgPLController.navigationBar.titleTextAttributes = attributes;
[_imgPLController.navigationBar setTintColor:Theme.sharedInstance.navigationBarTintColor];
[_imgPLController.navigationBar setBackgroundImage:[UIImage imageNamed:Theme.sharedInstance.navigationBarBackgroundImageName] forBarMetrics:UIBarMetricsDefault];
}
return _imgPLController;
}
~~~
生命周期:
~~~objective-c
- (void)viewDidLoad {
[super viewDidLoad];
self.title = @"扫一扫";
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:LOCALSTR(@"相册") style:UIBarButtonItemStylePlain target:self action:@selector(toAlbum)];
[self.view addSubview:self.scanView];
[self.scanDevice configPreViewLayerInView:self.view];
[self.scanDevice configOutputInterestRect:self.scanView.frame];
__weak typeof(self) weakSelf = self;
[self.scanDevice configWithDrawQRCodeFrame:true Result:^(QRCodeScanDevice *scanDevice, NSArray *results) {
__strong typeof(weakSelf) strongSelf = weakSelf;
[scanDevice stopScan];
[strongSelf.scanView endScanAnimation];
if (!results.count) return ;
NSString * resultStr = results[0];
[strongSelf handleQRCWithResultStr:resultStr];
}];
[self.view addSubview:self.alertLabel];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.scanDevice startScan];
[self.scanView beignScanAnimation];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
[self.scanDevice stopScan];
[self.scanView endScanAnimation];
}
~~~
处理:
~~~objective-c
- (void)handleQRCWithResultStr:(NSString *)resultStr {
// url
if ([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:resultStr]]) {
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:resultStr]];
}
else {
self.rm_completion(nil, resultStr);
}
NSMutableArray *vcs = [NSMutableArray arrayWithArray:self.navigationController.viewControllers];
[vcs removeLastObject];
self.navigationController.viewControllers = vcs.copy;
}
- (void)toAlbum {
[self presentViewController:self.imgPLController animated:true completion:nil];
}
- (void)cameraDidFinishedPickingImage:(UIImage *)image {
__weak typeof(self) weakSelf = self;
[QRCodeTool detectorWithImage:image DrawQRCodeFrame:true Result:^(UIImage *image, NSArray *results) {
__strong typeof(weakSelf) strongSelf = weakSelf;
if (!results.count) return ;
NSString * resultStr = results[0];
[strongSelf handleQRCWithResultStr:resultStr];
}];
}
~~~
相机相册代理:
~~~objective-c
#pragma mark - UIImagePickerControllerDelegate
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info {
/// 元数据类型
NSString * mediaType = [info objectForKey:UIImagePickerControllerMediaType];
if ([mediaType isEqualToString:(NSString *)kUTTypeImage]) {
UIImage * image;
if (picker.allowsEditing) { // 编辑后的图片
image = [info objectForKey:UIImagePickerControllerEditedImage];
}
else { // 原始图片
image = [info objectForKey:UIImagePickerControllerOriginalImage];
}
[self cameraDidFinishedPickingImage:image];
}
[picker dismissViewControllerAnimated:true completion:nil];
}
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker {
[picker dismissViewControllerAnimated:true completion:nil];
}
- (void)video:(NSString *)videoPath didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo {
if (error) {
NSLog(@"保存视频发生错误:%@",error.localizedDescription);
}
else {
NSLog(@"视频保存成功");
}
}
~~~
使用:
~~~objective-c
QRCodeScanViewController * scanVC = [QRCodeScanViewController new];
[self.viewController presentViewContoller:scanVC Animated:true];
~~~