1. 程式人生 > >iOS 二維碼掃描登入

iOS 二維碼掃描登入

二維碼掃描方面,其實客戶端能做的事情相對有限,基本上只需要完成掃描二維碼,獲取二維碼中的字串然後將該字串以及使用者id發給後端處理就好了。

二維碼掃描登入基本原理

首先介紹一下掃描登入的基本流程。
這裡寫圖片描述

  1. 網頁向伺服器請求二維碼生成。
  2. 伺服器生成一個qrcodeID,全域性唯一,能夠標誌該二維碼,並使用該qrcodeID生成一個二維碼。
  3. 使用者使用手機掃描該網頁上的二維碼(二維碼其實就是一串字元)獲得該qrcodeID。
  4. 手機將qrcodeID以及手機上的登入使用者ID(或者說是accessToken)傳回給伺服器。
  5. 這裡有兩種做法:
    • 就是從第一步開始,服務端與網頁就已經建立了長連線,然後服務端拿到qrcodeID以後(該qrcodeID由於全域性唯一不僅可以標誌該二維碼,同樣可以標誌該長連線是哪一個),服務端通過相應的長連線通知網頁拉取相應使用者的資訊並展示。
    • 網頁得到二維碼以後不斷的輪詢伺服器是否獲得授權(也就是使用者的ID),如果服務端已經拿到了授權則從服務端拉取相應的資訊。

客戶端實現

掃描流程一般會採用AVCaptureSession來完成,雖然imagePickerController也能完成照相的功能,但是由於它是蘋果自己封裝好的拍照工具,因此在自定義介面等功能會比較困難,因此這裡也採用AVCaptureSession來完成。
上程式碼:
定義好所需要的變數

//捕獲裝置,通常是前置攝像頭,後置攝像頭,麥克風(音訊輸入)
@property(nonatomic)AVCaptureDevice *device;

//AVCaptureDeviceInput 代表輸入裝置,他使用AVCaptureDevice 來初始化
@property(nonatomic)AVCaptureDeviceInput *input; //設定輸出型別為Metadata,因為這種輸出型別中可以設定掃描的型別,譬如二維碼 //當啟動攝像頭開始捕獲輸入時,如果輸入中包含二維碼,就會產生輸出 @property(nonatomic)AVCaptureMetadataOutput *output; //session:由他把輸入輸出結合在一起,並開始啟動捕獲裝置(攝像頭) @property(nonatomic)AVCaptureSession *session; //影象預覽層,實時顯示捕獲的影象 @property(nonatomic)AVCaptureVideoPreviewLayer *previewLayer; //遮罩層。黑色半透明的那部分view
@property(nonatomic)CALayer* maskLayer;

完成介面:

- (void)viewDidLoad {
    [super viewDidLoad];

    //掃描區域的大小
    scanImageViewW=260;
    scanImageViewH=260;
    scanImageViewX=(SCREEN_WIDTH-scanImageViewW)/2.0;
    scanImageViewY=200;

    [self creatCaptureDevice];

    //建立底層的實時展示捕獲畫面的layer
    self.previewLayer = [[AVCaptureVideoPreviewLayer alloc]initWithSession:self.session];
    self.previewLayer.frame = self.view.layer.bounds;
    self.previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
    [self.view.layer addSublayer:self.previewLayer];

    //建立遮罩層(中間留有空間用於掃描二維碼),需要一個可以畫介面的代理-(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx
    self.maskLayer=[[CALayer alloc]init];
    self.maskLayer.frame=self.view.layer.bounds;
    self.maskLayer.delegate=self;
    [self.view.layer insertSublayer:self.maskLayer above:_previewLayer];
    [self.maskLayer setNeedsDisplay];
    //開始啟動
    [self.session startRunning];

    UIImageView* scanImageView=[[UIImageView alloc]initWithFrame:CGRectMake(scanImageViewX,scanImageViewY,scanImageViewW,scanImageViewH)];
    scanImageView.image=[UIImage imageNamed:@"scanFrame"];
    _scanImageView=scanImageView;
    [self.view addSubview:scanImageView];

    UILabel* hintLabel=[[UILabel alloc]initWithFrame:CGRectMake(0, CGRectGetMaxY(scanImageView.frame)+30, 210, 20)];
    hintLabel.font=[UIFont systemFontOfSize:14];
    hintLabel.text[email protected]"將二維碼放入框內,即可自動掃描";
    hintLabel.textColor=[UIColor colorWithString:@"ffffff"];
    [hintLabel sizeToFit];
    CGPoint center=hintLabel.center;
    center.x=self.view.center.x;
    hintLabel.center=center;
    [self.view addSubview:hintLabel];

    //設定掃描區域的動畫效果
    CGFloat scanImageBarX=scanImageView.frame.origin.x;
    CGFloat scanImageBarY=scanImageView.frame.origin.y;
    CGFloat scanImageBarW=scanImageView.frame.size.width;
    CGFloat scanImageBarH=99;
    UIImageView* scanImageBar=[[UIImageView alloc]initWithImage:[UIImage imageNamed:@"scanBar"]];
    scanImageBar.frame=CGRectMake(scanImageBarX,scanImageBarY,scanImageBarW,scanImageBarH);
    [self.view addSubview:scanImageBar];
    _scanImageBar=scanImageBar;
    //iOS應該是對下面的動畫效果做了優化,感覺不是在CPU執行的該動畫
    [UIView animateWithDuration:2.0 delay:0 options:UIViewAnimationOptionRepeat animations:^{
        _scanImageBar.frame = CGRectMake(scanImageBarX, scanImageBarY + scanImageView.frame.size.height-scanImageBarH, scanImageBarW, scanImageBarH);
    } completion:nil];
    // Do any additional setup after loading the view.
}

#pragma mark -蒙版樣式代理
-(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx{
    if(layer==self.maskLayer){
        UIGraphicsBeginImageContextWithOptions(self.maskLayer.frame.size, NO, 1.0);
        CGContextSetFillColorWithColor(ctx, [UIColor colorWithRed:0 green:0 blue:0 alpha:0.5].CGColor);
        CGContextFillRect(ctx, self.maskLayer.frame);
        CGRect scanFrame =CGRectMake((SCREEN_WIDTH-260)/2.0,200,260,260);
        CGContextClearRect(ctx, scanFrame);
    }
}

指定裝置的輸入輸出

- (void)creatCaptureDevice{
    self.device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];

    self.input = [[AVCaptureDeviceInput alloc]initWithDevice:self.device error:nil];

    self.output = [[AVCaptureMetadataOutput alloc]init];

    [self.output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];

    //生成會話,用來結合輸入輸出
    self.session = [[AVCaptureSession alloc]init];
    if ([self.session canAddInput:self.input]) {
        [self.session addInput:self.input];
    }
    if ([self.session canAddOutput:self.output]) {
        [self.session addOutput:self.output];
    }

    //設定輸出媒體型別為二維碼
    [self.output setMetadataObjectTypes:@[AVMetadataObjectTypeQRCode]];

    //設定攝像頭的掃描區域,這個區域和正常的rect不一樣,需要按照比例來計算,基本上就是(Y,X,H,W),其中每一個字母比如Y表示中間掃描區域的y座標/螢幕的高度
    [self.output setRectOfInterest:CGRectMake(scanImageViewY/SCREEN_HEIGHT ,scanImageViewX/SCREEN_WIDTH, scanImageViewH/SCREEN_HEIGHT,scanImageViewW/SCREEN_WIDTH)];
}

#pragma mark 輸出的代理
//metadataObjects :把識別到的內容放到該陣列中
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection
{
    //停止掃描
    [self.session stopRunning];

    if ([metadataObjects count] >= 1) {
        //陣列中包含的都是AVMetadataMachineReadableCodeObject 型別的物件,該物件中包含解碼後的資料
        AVMetadataMachineReadableCodeObject *qrObject = [metadataObjects lastObject];
//        拿到掃描內容在這裡進行個性化處理,qrObject.stringValue就是掃描到的字串。
        NSLog("%@",qrObject.stringValue);
    }
}

-(void)dealloc{
    [_session stopRunning];

    if(_previewLayer){
        [_previewLayer removeFromSuperlayer];
    }
    if(self.maskLayer){
        self.maskLayer.delegate=nil;
    }
}