由於近期工作中遇到了個需求:需要將一些固定的字段 在多個移動端進行相互傳輸,所以就想到了 二維碼 這個神奇的東東! 現在的大街上、連個攤煎餅的大媽 都有自己的二維碼來讓大家進行掃碼支付。可見現在的二維碼使用率多高, 不光如此,在很多的社交類的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; }