前言
有關二維碼的介紹,我這裡不做過多說明, 可以直接去基維百科檢視,附上鍊接QR code.
IOS7之前,開發者進行掃碼程式設計時,一般會藉助第三方庫。常用的是ZBarSDKa和ZXingObjC,IOS7之後,系統的AVMetadataObject類中,為我們提供瞭解析二維碼的介面。經過測試,使用原生API掃描和處理的效率非常高,遠遠高於第三方庫。
掃描
官方提供的介面非常簡單,直接看程式碼,主要使用的是AVFoundation。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
@interface ViewController ()AVCaptureMetadataOutputObjectsDelegate>//用於處理採集資訊的代理 { AVCaptureSession * session;//輸入輸出的中間橋樑 } @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. //獲取攝像裝置 AVCaptureDevice * device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; //建立輸入流 AVCaptureDeviceInput * input = [AVCaptureDeviceInput deviceInputWithDevice:device error:nil]; if (!input) return; //建立輸出流 AVCaptureMetadataOutput * output = [[AVCaptureMetadataOutput alloc]init]; //設定代理 在主執行緒裡重新整理 [output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()]; //設定有效掃描區域 CGRect scanCrop=[self getScanCrop:_scanWindow.bounds readerViewBounds:self.view.frame]; output.rectOfInterest = scanCrop; //初始化連結物件 _session = [[AVCaptureSession alloc]init]; //高質量採集率 [_session setSessionPreset:AVCaptureSessionPresetHigh]; [_session addInput:input]; [_session addOutput:output]; //設定掃碼支援的編碼格式(如下設定條形碼和二維碼相容) output.metadataObjectTypes=@[AVMetadataObjectTypeQRCode,AVMetadataObjectTypeEAN13Code, AVMetadataObjectTypeEAN8Code, AVMetadataObjectTypeCode128Code]; AVCaptureVideoPreviewLayer * layer = [AVCaptureVideoPreviewLayer layerWithSession:_session]; layer.videoGravity=AVLayerVideoGravityResizeAspectFill; layer.frame=self.view.layer.bounds; [self.view.layer insertSublayer:layer atIndex:0]; //開始捕獲 [_session startRunning]; } -(void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection{ if (metadataObjects.count>0) { //[session stopRunning]; AVMetadataMachineReadableCodeObject * metadataObject = [metadataObjects objectAtIndex : 0 ]; //輸出掃描字串 NSLog(@"%@",metadataObject.stringValue); } } |
一些初始化的程式碼加上實現代理方法便完成了二維碼掃描的工作,這裡我們需要注意的是, 在二維碼掃描的時候, 我們一般都會在螢幕中間放一個方框,用來顯示二維碼掃描的大小區間,這裡我們在個AVCaptureMetadataOutput
類中有一個rectOfInterest
屬性,它的作用就是設定掃描範圍。
這個CGRect引數和普通的Rect範圍不太一樣,它的四個值的範圍都是0-1,表示比例。
rectOfInterest都是按照橫屏來計算的 所以當豎屏的情況下 x軸和y軸要交換一下。
寬度和高度設定的情況也是類似。
我們在上面設定有效掃描區域的方法如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#pragma mark-> 獲取掃描區域的比例關係 -(CGRect)getScanCrop:(CGRect)rect readerViewBounds:(CGRect)readerViewBounds { CGFloat x,y,width,height; x = (CGRectGetHeight(readerViewBounds)-CGRectGetHeight(rect))/2/CGRectGetHeight(readerViewBounds); y = (CGRectGetWidth(readerViewBounds)-CGRectGetWidth(rect))/2/CGRectGetWidth(readerViewBounds); width = CGRectGetHeight(rect)/CGRectGetHeight(readerViewBounds); height = CGRectGetWidth(rect)/CGRectGetWidth(readerViewBounds); return CGRectMake(x, y, width, height); } |
讀取
讀取主要用到CoreImage 不過要強調的是讀取二維碼的功能只有在iOS8之後才支援,我們需要在相簿中呼叫一個二維碼,將其讀取,程式碼如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
#pragma mark-> 我的相簿 -(void)myAlbum{ NSLog(@"我的相簿"); if([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary]){ //1.初始化相簿拾取器 UIImagePickerController *controller = [[UIImagePickerController alloc] init]; //2.設定代理 controller.delegate = self; //3.設定資源: /** UIImagePickerControllerSourceTypePhotoLibrary,相簿 UIImagePickerControllerSourceTypeCamera,相機 UIImagePickerControllerSourceTypeSavedPhotosAlbum,照片庫 */ controller.sourceType = UIImagePickerControllerSourceTypeSavedPhotosAlbum; //4.隨便給他一個轉場動畫 controller.modalTransitionStyle=UIModalTransitionStyleFlipHorizontal; [self presentViewController:controller animated:YES completion:NULL]; }else{ UIAlertView * alert = [[UIAlertView alloc]initWithTitle:@"提示" message:@"裝置不支援訪問相簿,請在設定->隱私->照片中進行設定!" delegate:nil cancelButtonTitle:@"確定" otherButtonTitles:nil, nil]; [alert show]; } } |
完成相簿代理, 我們在代理中新增讀取二維碼方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
#pragma mark-> imagePickerController delegate - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info { //1.獲取選擇的圖片 UIImage *image = info[UIImagePickerControllerOriginalImage]; //2.初始化一個監測器 CIDetector*detector = [CIDetector detectorOfType:CIDetectorTypeQRCode context:nil options:@{ CIDetectorAccuracy : CIDetectorAccuracyHigh }]; [picker dismissViewControllerAnimated:YES completion:^{ //監測到的結果陣列 NSArray *features = [detector featuresInImage:[CIImage imageWithCGImage:image.CGImage]]; if (features.count >=1) { /**結果物件 */ CIQRCodeFeature *feature = [features objectAtIndex:0]; NSString *scannedResult = feature.messageString; UIAlertView * alertView = [[UIAlertView alloc]initWithTitle:@"掃描結果" message:scannedResult delegate:nil cancelButtonTitle:@"確定" otherButtonTitles:nil, nil]; [alertView show]; } else{ UIAlertView * alertView = [[UIAlertView alloc]initWithTitle:@"提示" message:@"該圖片沒有包含一個二維碼!" delegate:nil cancelButtonTitle:@"確定" otherButtonTitles:nil, nil]; [alertView show]; } }]; } |
因為沒用真機,所以這裡沒有給出太多的截圖, 用模擬器讀取自帶圖片,結果如下
生成
生成二維碼,其實也是用到CoreImage,但是步驟繁瑣一些,程式碼如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 |
#pragma mark-> 二維碼生成 -(void)create{ UIImage *image=[UIImage imageNamed:@"6824500_006_thumb.jpg"]; NSString*tempStr; if(self.textField.text.length==0){ tempStr=@"ddddddddd"; }else{ tempStr=self.textField.text; } UIImage*tempImage=[QRCodeGenerator qrImageForString:tempStr imageSize:360 Topimg:image withColor:RandomColor]; _outImageView.image=tempImage; } +(UIImage*)qrImageForString:(NSString *)string imageSize:(CGFloat)size Topimg:(UIImage *)topimg withColor:(UIColor*)color{ if (![string length]) { return nil; } QRcode *code = QRcode_encodeString([string UTF8String], 0, QR_ECLEVEL_L, QR_MODE_8, 1); if (!code) { return nil; } // create context CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); CGContextRef ctx = CGBitmapContextCreate(0, size, size, 8, size * 4, colorSpace, kCGImageAlphaPremultipliedLast); CGAffineTransform translateTransform = CGAffineTransformMakeTranslation(0, -size); CGAffineTransform scaleTransform = CGAffineTransformMakeScale(1, -1); CGContextConcatCTM(ctx, CGAffineTransformConcat(translateTransform, scaleTransform)); // draw QR on this context [QRCodeGenerator drawQRCode:code context:ctx size:size withPointType:0 withPositionType:0 withColor:color]; // get image CGImageRef qrCGImage = CGBitmapContextCreateImage(ctx); UIImage * qrImage = [UIImage imageWithCGImage:qrCGImage]; if(topimg) { UIGraphicsBeginImageContext(qrImage.size); //Draw image2 [qrImage drawInRect:CGRectMake(0, 0, qrImage.size.width, qrImage.size.height)]; //Draw image1 float r=qrImage.size.width*35/240; [topimg drawInRect:CGRectMake((qrImage.size.width-r)/2, (qrImage.size.height-r)/2 ,r, r)]; qrImage=UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); } // some releases CGContextRelease(ctx); CGImageRelease(qrCGImage); CGColorSpaceRelease(colorSpace); QRcode_free(code); return qrImage; } + (void)drawQRCode:(QRcode *)code context:(CGContextRef)ctx size:(CGFloat)size withPointType:(QRPointType)pointType withPositionType:(QRPositionType)positionType withColor:(UIColor *)color { unsigned char *data = 0; int width; data = code->data; width = code->width; float zoom = (double)size / (code->width + 2.0 * qr_margin); CGRect rectDraw = CGRectMake(0, 0, zoom, zoom); // draw const CGFloat *components; if (color) { components = CGColorGetComponents(color.CGColor); }else { components = CGColorGetComponents([UIColor blackColor].CGColor); } CGContextSetRGBFillColor(ctx, components[0], components[1], components[2], 1.0); NSLog(@"aad :%f bbd :%f ccd:%f",components[0],components[1],components[2]); for(int i = 0; i for(int j = 0; j if(*data & 1) { rectDraw.origin = CGPointMake((j + qr_margin) * zoom,(i + qr_margin) * zoom); if (positionType == QRPositionNormal) { switch (pointType) { case QRPointRect: CGContextAddRect(ctx, rectDraw); break; case QRPointRound: CGContextAddEllipseInRect(ctx, rectDraw); break; default: break; } }else if(positionType == QRPositionRound) { switch (pointType) { case QRPointRect: CGContextAddRect(ctx, rectDraw); break; case QRPointRound: if ((i>=0 && i6 && j>=0 && j6) || (i>=0 && i6 && j>=width-7-1 && j-1) || (i>=width-7-1 && i-1 && j>=0 && j6)) { CGContextAddRect(ctx, rectDraw); }else { CGContextAddEllipseInRect(ctx, rectDraw); } break; default: break; } } } ++data; } } CGContextFillPath(ctx); } |
在textField輸入,生成下圖
長按二維碼識別
這個功能有很多的地方在用, 最讓人熟知的我想便是微信了,其實實現方法還是很簡單的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
#pragma mark-> 長按識別二維碼 -(void)dealLongPress:(UIGestureRecognizer*)gesture{ if(gesture.state==UIGestureRecognizerStateBegan){ _timer.fireDate=[NSDate distantFuture]; UIImageView*tempImageView=(UIImageView*)gesture.view; if(tempImageView.image){ //1. 初始化掃描器,設定設別型別和識別質量 CIDetector*detector = [CIDetector detectorOfType:CIDetectorTypeQRCode context:nil options:@{ CIDetectorAccuracy : CIDetectorAccuracyHigh }]; //2. 掃描獲取的特徵組 NSArray *features = [detector featuresInImage:[CIImage imageWithCGImage:tempImageView.image.CGImage]]; //3. 獲取掃描結果 CIQRCodeFeature *feature = [features objectAtIndex:0]; NSString *scannedResult = feature.messageString; UIAlertView * alertView = [[UIAlertView alloc]initWithTitle:@"掃描結果" message:scannedResult delegate:nil cancelButtonTitle:@"確定" otherButtonTitles:nil, nil]; [alertView show]; }else { UIAlertView * alertView = [[UIAlertView alloc]initWithTitle:@"掃描結果" message:@"您還沒有生成二維碼" delegate:nil cancelButtonTitle:@"確定" otherButtonTitles:nil, nil]; [alertView show]; } }else if (gesture.state==UIGestureRecognizerStateEnded){ _timer.fireDate=[NSDate distantPast]; } } |
我們用剛才生成的二維碼進行長按識別,效果如下
結語
本文demo下載地址請點這裡Demo,
轉自mokey1422所寫的仿支付寶二維碼。
系統原生的二維碼掃描掃描識別速度,要比第三方好用得多,在沒有特殊原因的情況下,比如7.0系統以下,我希望大家都能用系統原生的方法。
文章若有問題請給予指正,感謝。