iOS - QRCode 二維碼

一、QRCode

  • 在 iOS7 之前,在 iOS 中實現二維碼和條形碼掃描,咱們所知的有,兩大開源組件 ZBar 與 ZXing。iOS7 以後能夠利用系統原生 API 生成二維碼, iOS8 以後能夠生成條形碼, 系統默認生成的顏色是黑色。java

    • 一、ZBar 在掃描的靈敏度上,和內存的使用上相對於 ZXing 上都是較優的,可是對於 「圓角二維碼」 的掃描確很困難。
    • 二、ZXing 是 Google Code 上的一個開源的條形碼掃描庫,是用 java 設計的,連 Google Glass 都在使用的。但有人爲了追求更高效率以及可移植性,出現了 c++ port。Github 上的 Objectivc-C port,其實就是用 OC 代碼封裝了一下而已,並且已經中止維護。這樣效率很是低,在 instrument 下面能夠看到 CPU 和內存瘋漲,在內存小的機器上很容易崩潰。
    • 三、AVFoundation 不管在掃描靈敏度和性能上來講都是最優的,因此毫無疑問咱們應該切換到 AVFoundation,須要兼容 iOS 6 或以前的版本能夠用 ZBar 或 ZXing 代替。
  • 在 iOS8 + 系統中使用相機須要在 Info.plist 中添加 Privacy - Camera Usage Description,並設置其值。使用相冊須要在 Info.plist 中添加 Privacy - Photo Library Usage Description,並設置其值。c++

    QRCode24

  • 按照下圖在 Info.plist 文件中將 Localization native development region 的值改成 China。若是不設置此項彈出的相冊頁面中顯示的按鈕等爲英文菜單。git

    QRCode25

二、系統原生二維碼

2.1 掃描二維碼

  • 官方提供的接口很是簡單,直接看代碼,主要使用的是 AVFoundation。github

    // 包含頭文件
        #import <AVFoundation/AVFoundation.h>
    
        // 遵照協議
        <AVCaptureMetadataOutputObjectsDelegate>
    
        // 輸入輸出的中間橋樑
        @property (nonatomic, strong) AVCaptureSession *session;
    
        // 掃描窗口
        @property (nonatomic, strong) UIImageView *scanView;
    
        // 建立掃描視圖窗口,自定義方法
        - (void)createdScanView {
    
            CGFloat margin = 50;
            CGRect scanFrame = CGRectMake(margin,
                                          margin + 20,
                                          self.view.bounds.size.width - margin * 2,
                                          self.view.bounds.size.width - margin * 2);
    
            self.scanView = [[UIImageView alloc] initWithFrame:scanFrame];
    
            self.scanView.image = [UIImage imageNamed:@"scan_bg2"];
    
            [self.view addSubview:self.scanView];
    
            // 建立掃描
            [self startScan];
        }
    
        // 建立掃描,自定義方法
        - (void)startScan {
    
            // 獲取攝像設備
            AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
    
            // 建立輸入流
            AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device error:nil];
    
            // 建立輸出流
            AVCaptureMetadataOutput *output = [[AVCaptureMetadataOutput alloc] init];
    
            // 設置代理,在主線程裏刷新
            [output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
    
            // 設置有效掃描區域
            output.rectOfInterest = [self getScanCropWithScanViewFrame:self.scanView.frame readerViewBounds:self.view.bounds];
    
            // 初始化連接對象
            self.session = [[AVCaptureSession alloc] init];
    
            // 設置採集率,高質量
            [self.session setSessionPreset:AVCaptureSessionPresetHigh];
    
            [self.session addInput:input];
            [self.session addOutput:output];
    
            // 設置掃碼支持的編碼格式(以下設置條形碼和二維碼兼容)
            output.metadataObjectTypes = @[AVMetadataObjectTypeQRCode,
                                           AVMetadataObjectTypeEAN13Code,
                                           AVMetadataObjectTypeEAN8Code,
                                           AVMetadataObjectTypeCode128Code];
    
            AVCaptureVideoPreviewLayer *layer = [AVCaptureVideoPreviewLayer layerWithSession:self.session];
            layer.videoGravity = AVLayerVideoGravityResizeAspectFill;
            layer.frame = self.view.layer.bounds;
            [self.view.layer insertSublayer:layer atIndex:0];
    
            // 開始捕獲
            [self.session startRunning];
        }
    
        // 設置掃描區域的比例關係,自定義方法
        - (CGRect)getScanCropWithScanViewFrame:(CGRect)scanViewFrame readerViewBounds:(CGRect)readerViewBounds {
    
            CGFloat x, y, width, height;
    
            x = scanViewFrame.origin.y / readerViewBounds.size.height;
            y = scanViewFrame.origin.x / readerViewBounds.size.width;
            width = scanViewFrame.size.height / readerViewBounds.size.height;
            height = scanViewFrame.size.width / readerViewBounds.size.width;
    
            return CGRectMake(x, y, width, height);
        }
    
        // 獲取掃描結果,AVCaptureMetadataOutputObjectsDelegate 協議方法
        - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects
                                                                         fromConnection:(AVCaptureConnection *)connection {
    
            if (metadataObjects.count > 0) {
    
                // 中止掃描
                [self.session stopRunning];
    
                AVMetadataMachineReadableCodeObject *metadataObject = [metadataObjects objectAtIndex:0];
    
                // 獲取掃描結果
                NSString *resultString = metadataObject.stringValue;
    
                // 輸出掃描字符串
                [[[UIAlertView alloc] initWithTitle:@"掃描成功"
                                            message:resultString
                                           delegate:nil
                                  cancelButtonTitle:@"肯定"
                                  otherButtonTitles:nil] show];
            }
        }
  • 一些初始化的代碼加上實現代理方法便完成了二維碼掃描的工做,這裏咱們須要注意的是,在二維碼掃描的時候,咱們通常都會在屏幕中間放一個方框,用來顯示二維碼掃描的大小區間,這裏咱們在AVCaptureMetadataOutput 類中有一個 rectOfInterest 屬性,它的做用就是設置掃描範圍。這個 CGRect 參數和普通的 Rect 範圍不太同樣,它的四個值的範圍都是 0-1,表示比例。rectOfInterest 都是按照橫屏來計算的,因此當豎屏的狀況下 x 軸和 y 軸要交換一下,寬度和高度設置的狀況也是相似。微信

  • 效果session

    QRCode10 QRCode11

2.2 讀取二維碼

  • 讀取主要用到 CoreImage 不過要強調的是讀取二維碼的功能只有在 iOS8 以後才支持,咱們須要在相冊中調用一個二維碼,將其讀取,代碼以下框架

    // 遵照協議
        <UIImagePickerControllerDelegate, UINavigationControllerDelegate>
    
        // 打開相冊,選取圖片,自定義方法
        - (void)readQRCode {
    
            if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary]) {
    
                // 初始化相冊拾取器
                UIImagePickerController *picker = [[UIImagePickerController alloc] init];
    
                // 設置資源
                picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
    
                // 設置代理
                picker.delegate = self;
    
                [self presentViewController:picker animated:YES completion:nil];
    
            } else {
    
                NSString *errorStr = [NSString stringWithFormat:@"請在系統設置->隱私->照片中容許 \"%@\" 使用照片。",
                                      [[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString *)kCFBundleNameKey]];
    
                [[[UIAlertView alloc] initWithTitle:@"讀取失敗"
                                            message:errorStr
                                           delegate:nil
                                  cancelButtonTitle:@"肯定"
                                  otherButtonTitles:nil] show];
            }
        }
    
        // 獲取選中的圖片,imagePickerController 協議方法
        - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
    
            // 獲取選擇的圖片
            UIImage *image = info[UIImagePickerControllerOriginalImage];
    
            // 識別圖片中的二維碼
            NSString *resultString = [self recognizeQRCodeFromImage:image];
    
            // 輸出掃描字符串
            [[[UIAlertView alloc] initWithTitle:@"讀取成功"
                                        message:resultString
                                       delegate:nil
                              cancelButtonTitle:@"肯定"
                              otherButtonTitles:nil] show];
    
            // 返回
            [picker dismissViewControllerAnimated:YES completion:nil];
        }
    
        // 識別圖片中的二維碼
        - (NSString *)recognizeQRCodeFromImage:(UIImage *)image {
    
            CIImage *ciImage = [CIImage imageWithCGImage:image.CGImage];
    
            // 初始化掃描儀,設置識別類型和識別質量
            CIDetector *detector = [CIDetector detectorOfType:CIDetectorTypeQRCode
                                                      context:nil
                                                      options:@{CIDetectorAccuracy: CIDetectorAccuracyHigh}];
    
            // 掃描獲取的特徵組
            NSArray *features = [detector featuresInImage:ciImage];
    
            if (features.count >= 1) {
    
                // 獲取掃描結果
                CIQRCodeFeature *feature = [features objectAtIndex:0];
                NSString *resultString = feature.messageString;
    
                return resultString;
    
            } else {
    
                return @"該圖片不包含二維碼";
            }
        }
  • 效果ide

    QRCode12 QRCode13

2.3 長按識別二維碼

  • 這個功能有不少的地方在用,最讓人熟知的我想即是微信了,其實實現方法仍是很簡單的。性能

    // 建立圖片,添加長按手勢
        - (void)recognizeQRCode {
    
            CGFloat margin = 50;
            CGRect scanFrame = CGRectMake(margin,
                                          margin + 20,
                                          self.view.bounds.size.width - margin * 2,
                                          self.view.bounds.size.width - margin * 2);
    
            UIImageView *imageView = [[UIImageView alloc] initWithFrame:scanFrame];
            imageView.image = [UIImage imageNamed:@"demo"];
    
            imageView.userInteractionEnabled = YES;
    
            [self.view addSubview:imageView];
    
            UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self 
                                                                                                    action:@selector(dealLongPress:)];
            [imageView addGestureRecognizer:longPress];
        }
    
        // 處理長按手勢,識別圖片中的二維碼
        - (void)dealLongPress:(UIGestureRecognizer *)gesture {
    
            if (gesture.state == UIGestureRecognizerStateBegan) {
    
                UIImageView *pressedImageView = (UIImageView *)gesture.view;
    
                if (pressedImageView.image) {
    
                    // 識別圖片中的二維碼
                    NSString *resultString = [self recognizeQRCodeFromImage:pressedImageView.image];
    
                    // 輸出掃描字符串
                    [[[UIAlertView alloc] initWithTitle:@"識別成功"
                                                message:resultString
                                               delegate:nil
                                      cancelButtonTitle:@"肯定"
                                      otherButtonTitles:nil] show];
    
                } else {
                    [[[UIAlertView alloc] initWithTitle:@"識別失敗"
                                                message:@"該圖片不包含二維碼"
                                               delegate:nil
                                      cancelButtonTitle:@"肯定"
                                      otherButtonTitles:nil] show];
                }
            }
        }
    
        // 識別圖片中的二維碼
        - (NSString *)recognizeQRCodeFromImage:(UIImage *)image {
    
            CIImage *ciImage = [CIImage imageWithCGImage:image.CGImage];
    
            // 初始化掃描儀,設置識別類型和識別質量
            CIDetector *detector = [CIDetector detectorOfType:CIDetectorTypeQRCode
                                                      context:nil
                                                      options:@{CIDetectorAccuracy: CIDetectorAccuracyHigh}];
    
            // 掃描獲取的特徵組
            NSArray *features = [detector featuresInImage:ciImage];
    
            if (features.count >= 1) {
    
                // 獲取掃描結果
                CIQRCodeFeature *feature = [features objectAtIndex:0];
                NSString *resultString = feature.messageString;
    
                return resultString;
    
            } else {
    
                return @"該圖片不包含二維碼";
            }
        }
  • 效果ui

    QRCode14 QRCode15

2.4 生成二維碼

  • 生成二維碼,其實也是用到 CoreImage,可是步驟繁瑣一些,代碼以下

    // 建立 ImageView,存放生成的二維碼
        - (void)createQRCode {
    
            CGFloat margin = 50;
            CGRect frame = CGRectMake(margin,
                                      margin + 20,
                                      self.view.bounds.size.width - margin * 2,
                                      self.view.bounds.size.width - margin * 2);
    
            UIImageView *imageView = [[UIImageView alloc] initWithFrame:frame];
            [self.view addSubview:imageView];
    
            // 生成二維碼
            [self createQRCodeToImageView:imageView
                               fromString:@"qianchia"
                                 withIcon:[UIImage imageNamed:@"demo1"]
                                withColor:[UIColor redColor]];
        }
    
        // 生成二維碼
        - (void)createQRCodeToImageView:(UIImageView *)imageView
                             fromString:(NSString *)inputString
                               withIcon:(UIImage *)icon
                              withColor:(UIColor *)color {
    
            // 建立過濾器
            CIFilter *filter = [CIFilter filterWithName:@"CIQRCodeGenerator"];
    
            // 恢復默認
            [filter setDefaults];
    
            // 給過濾器添加數據
            NSData *data = [inputString dataUsingEncoding:NSUTF8StringEncoding];
    
            // 經過 KVO 設置濾鏡 inputMessage 數據
            [filter setValue:data forKey:@"inputMessage"];
    
            // 設置二維碼顏色
            UIColor *onColor = color ? : [UIColor blackColor];
            UIColor *offColor = [UIColor whiteColor];
    
            CIFilter *colorFilter = [CIFilter filterWithName:@"CIFalseColor"
                                               keysAndValues:@"inputImage", filter.outputImage,
                                                             @"inputColor0", [CIColor colorWithCGColor:onColor.CGColor],
                                                             @"inputColor1", [CIColor colorWithCGColor:offColor.CGColor],
                                                             nil];
    
            // 獲取輸出的二維碼
            CIImage *outputImage = colorFilter.outputImage;
            // CIImage *outputImage = [filter outputImage];
    
            CIContext *context = [CIContext contextWithOptions:nil];
            CGImageRef cgImage = [context createCGImage:outputImage
                                               fromRect:[outputImage extent]];
    
            // 將 CIImage 轉換成 UIImage,並放大顯示
            UIImage *qrImage = [UIImage imageWithCGImage:cgImage
                                                 scale:1.0
                                           orientation:UIImageOrientationUp];
    
            // 重繪 UIImage,默認狀況下生成的圖片比較模糊
            CGFloat scale = 100;
            CGFloat width = qrImage.size.width * scale;
            CGFloat height = qrImage.size.height * scale;
    
            UIGraphicsBeginImageContext(CGSizeMake(width, height));
            CGContextRef context1 = UIGraphicsGetCurrentContext();
            CGContextSetInterpolationQuality(context1, kCGInterpolationNone);
            [qrImage drawInRect:CGRectMake(0, 0, width, height)];
            qrImage = UIGraphicsGetImageFromCurrentImageContext();
            UIGraphicsEndImageContext();
            CGImageRelease(cgImage);
    
            // 添加頭像
    
            if (icon) {
                UIGraphicsBeginImageContext(qrImage.size);
                [qrImage drawInRect:CGRectMake(0, 0, qrImage.size.width, qrImage.size.height)];
    
                // 設置頭像大小
                CGFloat scale = 5;
                CGFloat width = qrImage.size.width / scale;
                CGFloat height = qrImage.size.height / scale;
                CGFloat x = (qrImage.size.width - width) / 2;
                CGFloat y = (qrImage.size.height - height) / 2;
                [icon drawInRect:CGRectMake( x,  y, width, height)];
    
                UIImage *newImage =  UIGraphicsGetImageFromCurrentImageContext();
                UIGraphicsEndImageContext();
    
                imageView.image = newImage;
    
            } else {
                imageView.image = qrImage;
            }
        }
  • 效果

    QRCode16 QRCode17

2.5 對原生二維碼的封裝

  • 具體實現代碼見 GitHub 源碼 QExtension

    // 包含頭文件
        #import "QExtension.h"
  • 掃描/識別二維碼

    // 建立二維碼掃描視圖控制器
        QQRCode *qrCode = [QQRCode q_qrCodeWithResult:^(BOOL isSucceed, NSString *result) {
    
            if (isSucceed) {
    
                [[[UIAlertView alloc] initWithTitle:@"Succeed"
                                            message:result
                                           delegate:nil
                                  cancelButtonTitle:@"肯定"
                                  otherButtonTitles:nil] show];
    
            } else {
    
                [[[UIAlertView alloc] initWithTitle:@"Failed"
                                            message:result
                                           delegate:nil
                                  cancelButtonTitle:@"肯定"
                                  otherButtonTitles:nil] show];
            }
        }];
    
        // 設置個人二維碼信息
        qrCode.myQRCodeInfo = @"http://weixin.qq.com/r/xUqbg1-ENgJJrRvg9x-X";
        qrCode.headIcon = [UIImage imageNamed:@"demo6"];
    
        // 打開掃描視圖控制器
        [self presentViewController:qrCode animated:YES completion:nil];
    • 效果

      QRCode18 QRCode19

      QRCode20 QRCode21

  • 識別二維碼

    // 建立圖片,添加長按手勢
        self.imageView.image = [UIImage imageNamed:@"demo4"];
    
        UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(dealLongPress:)];
        [self.imageView addGestureRecognizer:longPress];
    
        // 處理長按手勢,識別圖片中的二維碼
        - (void)dealLongPress:(UIGestureRecognizer *)gesture {
    
            if (gesture.state == UIGestureRecognizerStateBegan) {
    
                UIImageView *pressedImageView = (UIImageView *)gesture.view;
                UIImage *image = pressedImageView.image;
    
                // 識別圖片中的二維碼
                NSString *result = [image q_stringByRecognizeQRCode];
    
                [[[UIAlertView alloc] initWithTitle:@"Succeed"
                                            message:result
                                           delegate:nil
                                  cancelButtonTitle:@"肯定"
                                  otherButtonTitles:nil] show];
            }
        }
    • 效果

      QRCode23

  • 生成二維碼

    // 生成普通的二維碼
        UIImage *qrImage = [UIImage q_imageWithQRCodeFromString:@"http://weixin.qq.com/r/xUqbg1-ENgJJrRvg9x-X"
                                                       headIcon:nil
                                                          color:nil
                                                      backColor:nil];
    
        // 生成帶頭像的二維碼
        UIImage *qrImage = [UIImage q_imageWithQRCodeFromString:@"http://weixin.qq.com/r/xUqbg1-ENgJJrRvg9x-X"
                                                       headIcon:[UIImage imageNamed:@"demo6"]
                                                          color:[UIColor blackColor]
                                                      backColor:[UIColor whiteColor]];
    
        // 生成指定圖片大小的二維碼
        UIImage *qrImage = [UIImage q_imageWithQRCodeFromString:@"http://weixin.qq.com/r/xUqbg1-ENgJJrRvg9x-X"
                                                      imageSize:CGSizeMake(2048, 2048)
                                                       headIcon:[UIImage imageNamed:@"demo6"]
                                                      headFrame:CGRectMake(819, 819, 410, 410)
                                                          color:nil
                                                      backColor:nil];
    • 效果

      QRCode28 QRCode22

2.6 對原生條形碼的封裝

  • 具體實現代碼見 GitHub 源碼 QExtension

    // 包含頭文件
        #import "QExtension.h"
  • 掃描條形碼

    • 同上面 2.5 的 "掃描/識別二維碼"。
  • 生成條形碼

    // 生成條形碼
        UIImage *qrImage = [UIImage q_imageWithBarCodeFromString:@"cnblogs: QianChia"
                                                           color:nil
                                                       backColor:nil];
    
        // 生成指定圖片大小的條形碼
        UIImage *qrImage = [UIImage q_imageWithBarCodeFromString:@"cnblogs: QianChia"
                                                       imageSize:CGSizeMake(1024, 512)
                                                           color:[UIColor blueColor]
                                                       backColor:[UIColor redColor]];
    • 效果

      QRCode26 QRCode27

三、ZBarSDK 二維碼使用

  • 使用 ZBarSDK,能夠掃描條形碼和二維碼。

  • 配置

    • 一、添加 SDK 的依賴庫和框架。在 項目設置 => TARGETS => Build Phases => Link Binary With Libraries 中依次添加如下庫或框架:

      AVFoundation.framework
          CoreMedia.framework
          CoreVideo.framework
          QuartzCore.framework
          libiconv.tbd

      QRCode1

    • 二、因爲 ZBarSDK 不支持 Bitcode,須要在 Xcode 的項目設置 => TARGETS => Build Settings => Build Options => Enable Bitcode 的值設置爲 NO,不然沒法進行真機調試。

      QRCode2

    • 三、在 iOS8 + 系統中使用相機須要在 Info.plist 中添加 Privacy - Camera Usage Description,並設置其值。

      QRCode3

    • 四、在須要使用 ZBarSDK 的文件中

      // 包含頭文件
          #import "ZBarSDK.h"
      
          // 遵照協議
          <ZBarReaderViewDelegate>
  • 建立掃描

    // 聲明掃描視圖
        @property (nonatomic, strong) ZBarReaderView *scanView;
    
        // 實例化掃描視圖
        self.scanView = [[ZBarReaderView alloc] init];
    
        // 設置掃描視圖的位置尺寸
        float width = self.view.bounds.size.width - 40;
        self.scanView.frame = CGRectMake(20, 30, width, width);
    
        // 設置代理人
        self.scanView.readerDelegate = self;
    
        // 添加掃描視圖
        [self.view addSubview:self.scanView];
    
        // 關閉閃光燈
        self.scanView.torchMode = 0;
    
        // 開始掃描
        [self.scanView start];
  • 獲取掃描結果

    // ZBarReaderViewDelegate 協議方法
        - (void)readerView:(ZBarReaderView *)readerView didReadSymbols:(ZBarSymbolSet *)symbols fromImage:(UIImage *)image {
    
            for (ZBarSymbol *symbol in symbols) {
    
                // 獲取掃描結果
                NSString *scanString = symbol.data;
    
                // 顯示掃描結果
                self.scanResult.text = scanString;
            }
    
            // 中止掃描
            [self.scanView stop];
        }
  • 效果

    QRCode6 QRCode7

四、ZCZBarSDK 二維碼使用

  • 使用 ZCZBarSDK,能夠掃描條形碼和二維碼。

  • 配置

    • 一、添加 SDK 的依賴庫和框架。在 項目設置 => TARGETS => Build Phases => Link Binary With Libraries 中依次添加如下庫或框架:

      libiconv.tbd

      QRCode4

    • 二、因爲 ZCZBarSDK 不支持 Bitcode,須要在 Xcode 的項目設置 => TARGETS => Build Settings => Build Options => Enable Bitcode 的值設置爲 NO,不然沒法進行真機調試。

      QRCode2

    • 三、在 iOS8 + 系統中使用相機須要在 Info.plist 中添加 Privacy - Camera Usage Description,並設置其值。使用相冊須要在 Info.plist 中添加 Privacy - Photo Library Usage Description,並設置其值。

      QRCode5

    • 四、在須要使用 ZCZBarSDK 的文件中

      // 包含頭文件
          #import "ZCZBarViewController.h"
  • 生成二維碼

    // 將字符串 self.detailTF.text 中的內容生成的二維碼存放到 ImageView(self.codeImageView)中。
    
        if (self.detailTF.text.length != 0) {
    
            [ZCZBarViewController createImageWithImageView:self.codeImageView String:self.detailTF.text Scale:100];
        }
  • 掃描二維碼

    // isQRCode: 是否關閉條形碼掃描,專門掃描二維碼。
    
        ZCZBarViewController *zzvc = [[ZCZBarViewController alloc] initWithIsQRCode:NO Block:^(NSString *result, BOOL isSucceed) {
    
            if (isSucceed) {
    
                self.scanResult.text = [NSString stringWithFormat:@"%@", result];
            }
        }];
    
        // 打開掃描視圖控制器
        [self presentViewController:zzvc animated:YES completion:nil];
  • 效果

    QRCode8 QRCode9

相關文章
相關標籤/搜索