由於近期工作中遇到了個需求:需要將一些固定的字段 在多個移動端進行相互傳輸,所以就想到了 二維碼 這個神奇的東東! 現在的大街上、連個攤煎餅的大媽 都有自己的二維碼來讓大家進行掃碼支付。可見現在的二維碼使用率多高, 不光如此,在很多的社交類的APP 基本都有掃一掃加好友這個功能吧,因此決定學一學這個神奇的東西。
查找了一些資料博客啊發現,iOS7之前 對於開發人員來說 熟悉的第三方QRCode庫有:
但是iOS7之后呢,系統框架已經集成二維碼的生成與讀取, 這使開發變得方便很多, 並且會比第三方更加效率。今天就來講講用系統原生的方式 來實現二維碼的生成和掃描吧
( 一 )高清二維碼
系統二維碼主要通過 CIFilter 的對象來完成, 當然首先我們需要先導入這個類所在的框架,並實現下面的代碼
#import <CoreImage/CoreImage.h>
// 1.創建過濾器 -- 蘋果沒有將這個字符定義為常量
CIFilter *filter = [CIFilter filterWithName:@"CIQRCodeGenerator"];
// 2.過濾器恢復默認設置
[filter setDefaults];
// 3.給過濾器添加數據(正則表達式/帳號和密碼) -- 通過KVC設置過濾器,只能設置NSData類型
NSString *dataString = @"http://www.baidu.com";
NSData *data = [dataString dataUsingEncoding:NSUTF8StringEncoding];
[filter setValue:data forKeyPath:@"inputMessage"];
// 4.獲取輸出的二維碼
CIImage *outputImage = [filter outputImage];
// 5.顯示二維碼
UIImage *image = [UIImage imageWithCIImage:outputImage];

通過上面這種最簡單的方式 生成的二維碼很模糊,而且二維碼的大小也不方便控制,
對於我們來說,需求的是一張 能控制大小,並且高清顯示的二維碼,因此我們需要用一種方式 將CIImage 轉為我們心目中那個UIImage
/** 根據CIImage生成指定大小的UIImage */
+ (UIImage *)createNonInterpolatedUIImageFormCIImage:(CIImage *)image withSize:(CGFloat)size {
CGRect extent = CGRectIntegral(image.extent);
CGFloat scale = MIN(size/CGRectGetWidth(extent), size/CGRectGetHeight(extent));
// 1.創建bitmap;
size_t width = CGRectGetWidth(extent) * scale;
size_t height = CGRectGetHeight(extent) * scale;
CGColorSpaceRef cs = CGColorSpaceCreateDeviceGray();
CGContextRef bitmapRef = CGBitmapContextCreate(nil, width, height, 8, 0, cs, (CGBitmapInfo)kCGImageAlphaNone);
CIContext *context = [CIContext contextWithOptions:nil];
CGImageRef bitmapImage = [context createCGImage:image fromRect:extent];
CGContextSetInterpolationQuality(bitmapRef, kCGInterpolationNone);
CGContextScaleCTM(bitmapRef, scale, scale);
CGContextDrawImage(bitmapRef, extent, bitmapImage);
// 2.保存bitmap到圖片
CGImageRef scaledImage = CGBitmapContextCreateImage(bitmapRef);
CGContextRelease(bitmapRef);
CGImageRelease(bitmapImage);
return [UIImage imageWithCGImage:scaledImage];
}

( 二 )彩色二維碼
在使用過程中發現了個問題, 就是當我們使用一個長度過長的字段 去生成高清二維碼的時候,這個二維碼 就會變得非常密集、濃稠, 用手機來掃描的時候,由於手機攝像頭像素問題很難讀取到這個二維碼,因此我需要一個 彩色二維碼來增加它的辨識度。
// 1、創建濾鏡對象
CIFilter *filter = [CIFilter filterWithName:@"CIQRCodeGenerator"];
// 恢復濾鏡的默認屬性
[filter setDefaults];
// 2、設置數據
NSString *string_data = @"http://www.baidu.com";
NSData *qrImageData = [string_data dataUsingEncoding:NSUTF8StringEncoding];
// 設置過濾器的輸入值, KVC賦值
[filter setValue:qrImageData forKey:@"inputMessage"];
// 3、獲得濾鏡輸出的圖像
CIImage *outputImage = [filter outputImage];
// 圖片小於(27,27),我們需要放大
outputImage = [outputImage imageByApplyingTransform:CGAffineTransformMakeScale(9, 9)];
// 4、創建彩色過濾器(彩色的用的不多)
CIFilter * color_filter = [CIFilter filterWithName:@"CIFalseColor"];
// 設置默認值
[color_filter setDefaults];
// 5、KVC 給私有屬性賦值
[color_filter setValue:outputImage forKey:@"inputImage"];
// 6、需要使用 CIColor 為背景顏色 和 主顏色 上色
// inputColor0:背景顏色 ,inputColor1 主顏色
// 注意不要使用 [CIColor redColor][CIColor blueColor],這些類似於UIColor的方法只有在iOS 10系統才有
[color_filter setValue:[CIColor colorWithRed:1 green:1 blue:1] forKey:@"inputColor0"];
[color_filter setValue:[CIColor colorWithRed:0 green:0 blue:1] forKey:@"inputColor1"];
// 7、設置輸出
CIImage *colorImage = [color_filter outputImage];
//8、輸出UIImage
UIImage *image = [UIImage imageWithCIIimage:colorImage];
掃描二維碼
掃描主要使用的是AVFoundation 使用起來也非常的簡單 ,通過設置<AVCaptureMetadataOutputObjectsDelegate>代理可以監聽掃描到的二維碼中的信息
#import "ViewController.h"
#import <AVFoundation/AVFoundation.h>
@interface ViewController () <AVCaptureMetadataOutputObjectsDelegate>
/// 會話對象
@property (nonatomic, strong) AVCaptureSession *session;
/// 圖層類
@property (nonatomic, strong) AVCaptureVideoPreviewLayer *previewLayer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
// 1、獲取攝像設備
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
// 2、創建輸入流
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device error:nil];
// 3、創建輸出流
AVCaptureMetadataOutput *output = [[AVCaptureMetadataOutput alloc] init];
// 4、設置代理 在主線程里刷新
[output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
// 設置掃描范圍(每一個取值0~1,以屏幕右上角為坐標原點)
// 注:微信二維碼的掃描范圍是整個屏幕,這里並沒有做處理(可不用設置)
output.rectOfInterest = CGRectMake(0.05, 0.2, 0.7, 0.6);
// 5、初始化鏈接對象(會話對象)
self.session = [[AVCaptureSession alloc] init];
// 高質量采集率
[_session setSessionPreset:AVCaptureSessionPresetHigh];
// 5.1 添加會話輸入
[_session addInput:input];
// 5.2 添加會話輸出
[_session addOutput:output];
// 6、設置輸出數據類型,需要將元數據輸出添加到會話后,才能指定元數據類型,否則會報錯
// 設置掃碼支持的編碼格式(如下設置條形碼和二維碼兼容)
output.metadataObjectTypes = @[AVMetadataObjectTypeQRCode, AVMetadataObjectTypeEAN13Code, AVMetadataObjectTypeEAN8Code, AVMetadataObjectTypeCode128Code];
// 7、實例化預覽圖層, 傳遞_session是為了告訴圖層將來顯示什么內容
self.previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:_session];
_previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
_previewLayer.frame = self.view.layer.bounds;
// 8、將圖層插入當前視圖
[self.view.layer insertSublayer:_previewLayer atIndex:0];
// 9、啟動會話
[_session startRunning];
}
#pragma mark - 獲取掃描結果
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection
{
if (metadataObjects.count > 0) {
AVMetadataMachineReadableCodeObject *object = [metadataObjects lastObject];
NSLog(@"%@", object.stringValue);
}
}
@end
AVCaptureMetadataOutput 有個屬性 rectOfInterest 他是用來控制你屏幕掃描的范圍的,默認是按照整個屏幕來掃描,rectOfInterest的值的范圍都是0-1 是按比例取值而不是實際尺寸 不過其實也很簡單 只要換算一下就好了 ,這里唯一要注意的一點是 rectOfInterest 都是按照橫屏來計算的 所以當豎屏的情況下 x軸和y軸要交換一下
讀取二維碼
讀取主要用到CoreImage 不過要強調的是讀取二維碼的功能只有在iOS8之后才支持,讀取的代碼也非常的簡單
//首先拿到 我們需要讀取的那個圖片
UIImage * srcImage = qrcodeImage;
CIContext *context = [CIContext contextWithOptions:nil];
// CIDetector(CIDetector可用於人臉識別)進行圖片解析,聲明一個CIDetector,並設定識別類型 CIDetectorTypeQRCode
CIDetector *detector = [CIDetector detectorOfType:CIDetectorTypeQRCode context:context options:@{CIDetectorAccuracy:CIDetectorAccuracyHigh}];
CIImage *image = [CIImage imageWithCGImage:srcImage.CGImage];
// 取得識別結果是個數組
NSArray *features = [detector featuresInImage:[CIImage imageWithCGImage:image.CGImage]];
for (int index = 0; index < [features count]; index ++) {
CIQRCodeFeature *feature = [features objectAtIndex:index];
//這個String就是我們從二維碼中獲取到的信息
NSString *scannedResult = feature.messageString;
}
