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=@"将二维码放入框内,即可自动扫描";
    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;
    }
}

猜你喜欢

转载自blog.csdn.net/arceushs/article/details/78430566