二维码扫描方面,其实客户端能做的事情相对有限,基本上只需要完成扫描二维码,获取二维码中的字符串然后将该字符串以及用户id发给后端处理就好了。
二维码扫描登录基本原理
首先介绍一下扫描登录的基本流程。
- 网页向服务器请求二维码生成。
- 服务器生成一个qrcodeID,全局唯一,能够标志该二维码,并使用该qrcodeID生成一个二维码。
- 用户使用手机扫描该网页上的二维码(二维码其实就是一串字符)获得该qrcodeID。
- 手机将qrcodeID以及手机上的登录用户ID(或者说是accessToken)传回给服务器。
- 这里有两种做法:
- 就是从第一步开始,服务端与网页就已经建立了长连接,然后服务端拿到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;
}
}