iOS:二维码

Page content

传统互联网的入口在浏览器,移动互联网的入口在二维码

前言

在iOS7.0前,实现二维码扫描还需要借助第三方库,但如今,苹果的API逐渐完善与强大,这一功能我们可以借助AVFoundation框架来搞定。

生成

~~~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];
~~~