扫描二维码ViewController
#import <UIKit/UIKit.h>
typedef void(^TakeQRCodeInfo)(id _Nonnull item);
NS_ASSUME_NONNULL_BEGIN
@interface ScanQRCodeVC : RootVC
/**
@brief 扫描区域高度
*/
@property (nonatomic, assign)CGFloat scanY;
/**
@brief 扫描范围
*/
@property (nonatomic, assign)CGSize scanSize;
/**
@brief 获取二维码信息回调
*/
@property (copy, nonatomic) TakeQRCodeInfo takeBlock;
@end
#define SCREEN_WIDTH [UIScreen mainScreen].bounds.size.width
/* 屏幕高 */
#define SCREENH_HEIGHT [UIScreen mainScreen].bounds.size.height
#import "ScanQRCodeVC.h"
#import <AVFoundation/AVFoundation.h>
#import "QRCodeScanView.h"
#import "QRCodeBackgroundView.h"
@interface ScanQRCodeVC ()<AVCaptureMetadataOutputObjectsDelegate>
@property(nonatomic,strong)AVCaptureDevice *device;//创建相机
@property(nonatomic,strong)AVCaptureDeviceInput *input;//创建输入设备
@property(nonatomic,strong)AVCaptureMetadataOutput *output;//创建输出设备
@property(nonatomic,strong)AVCaptureSession *session;//创建捕捉类
@property(strong,nonatomic)AVCaptureVideoPreviewLayer *preview;//视觉输出预览层
@property(strong,nonatomic)QRCodeScanView *scanView;//自定义的扫描视图
@end
@implementation ScanQRCodeVC
static CGFloat ScanHeight;
static CGFloat ScanWidth;
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
ScanHeight = _scanSize.height;
ScanWidth = _scanSize.width;
//穿件扫描区域
[self createScan];
[self capture];
//添加返回按钮
[self.view bringSubviewToFront:self.navigationBar];
[self onNavigationBarColorChange:[UIColor clearColor]];
[self.navigationBar setBackgroundImage:[UIImage new] forBarMetrics:UIBarMetricsDefault];
[self.navigationBar setShadowImage:[UIImage new]];
self.titleLabel.text = @"扫一扫";
self.titleLabel.textColor = [UIColor whiteColor];
[self.backBtn setImage:[UIImage imageNamed:@"backWhiteBtnImage"] forState:UIControlStateNormal];
}
#pragma mark - 初始化扫描设备
- (void)capture {
//如果是模拟器返回(模拟器获取不到摄像头)
if (TARGET_IPHONE_SIMULATOR) {
return;
}
// 下面的是比较重要的,也是最容易出现崩溃的原因,就是我们的输出流的类型
// 1.这里可以设置多种输出类型,这里必须要保证session层包括输出流
// 2.必须要当前项目访问相机权限必须通过,所以最好在程序进入当前页面的时候进行一次权限访问的判断
AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
if(authStatus ==AVAuthorizationStatusRestricted|| authStatus ==AVAuthorizationStatusDenied){
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"提示" message:@"请在iPhone的“设置”-“隐私”-“相机”功能中,打开APP相机访问权限" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *sureAction = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleCancel handler:nil];
[alert addAction:sureAction];
[self presentViewController:alert animated:YES completion:nil];
return;
}
//初始化基础"引擎"Device
self.device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
//初始化输入流 Input,并添加Device
self.input = [AVCaptureDeviceInput deviceInputWithDevice:self.device error:nil];
//初始化输出流Output
self.output = [[AVCaptureMetadataOutput alloc] init];
//设置输出流的相关属性
// 确定输出流的代理和所在的线程,这里代理遵循的就是上面我们在准备工作中提到的第一个代理,至于线程的选择,建议选在主线程,这样方便当前页面对数据的捕获.
[self.output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
//设置扫描区域的大小 rectOfInterest 默认值是CGRectMake(0, 0, 1, 1) 按比例设置
self.output.rectOfInterest = CGRectMake(_scanY/SCREENH_HEIGHT,((SCREEN_WIDTH-ScanWidth)/2)/SCREEN_WIDTH,ScanHeight/SCREENH_HEIGHT,ScanWidth/SCREEN_WIDTH);
// 初始化session
self.session = [[AVCaptureSession alloc]init];
// 设置session类型,AVCaptureSessionPresetHigh 是 sessionPreset 的默认值。
[_session setSessionPreset:AVCaptureSessionPresetHigh];
//将输入流和输出流添加到session中
// 添加输入流
if ([_session canAddInput:self.input]) {
[_session addInput:self.input];
}
// 添加输出流
if ([_session canAddOutput:self.output]) {
[_session addOutput:self.output];
//扫描格式
NSMutableArray *metadataObjectTypes = [NSMutableArray array];
[metadataObjectTypes addObjectsFromArray:@[AVMetadataObjectTypeQRCode,AVMetadataObjectTypeEAN13Code,AVMetadataObjectTypeEAN8Code,AVMetadataObjectTypeCode128Code,AVMetadataObjectTypeCode39Code,AVMetadataObjectTypeCode93Code,AVMetadataObjectTypeCode39Mod43Code,AVMetadataObjectTypePDF417Code,AVMetadataObjectTypeAztecCode,AVMetadataObjectTypeUPCECode,]];
// >= ios 8
if (floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_7_1) {
[metadataObjectTypes addObjectsFromArray:@[AVMetadataObjectTypeInterleaved2of5Code,AVMetadataObjectTypeITF14Code,AVMetadataObjectTypeDataMatrixCode]];
}
//设置扫描格式
self.output.metadataObjectTypes= metadataObjectTypes;
}
//设置输出展示平台AVCaptureVideoPreviewLayer
// 初始化
self.preview =[AVCaptureVideoPreviewLayer layerWithSession:_session];
// 设置Video Gravity,顾名思义就是视频播放时的拉伸方式,默认是AVLayerVideoGravityResizeAspect
// AVLayerVideoGravityResizeAspect 保持视频的宽高比并使播放内容自动适应播放窗口的大小。
// AVLayerVideoGravityResizeAspectFill 和前者类似,但它是以播放内容填充而不是适应播放窗口的大小。最后一个值会拉伸播放内容以适应播放窗口.
// 因为考虑到全屏显示以及设备自适应,采用fill填充
self.preview.videoGravity =AVLayerVideoGravityResizeAspectFill;
// 设置展示平台的frame
self.preview.frame = CGRectMake(0, 0, SCREEN_WIDTH, SCREENH_HEIGHT);
// 因为 AVCaptureVideoPreviewLayer是继承CALayer,所以添加到当前view的layer层
[self.view.layer insertSublayer:self.preview atIndex:0];
//开始
[self.session startRunning];
}
#pragma mark - AVCaptureMetadataOutputObjectsDelegate
#pragma mark - 扫描结果处理
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection {
//扫描成功播放音效
AudioServicesPlaySystemSound(1054);
// 判断扫描结果的数据是否存在
if ([metadataObjects count] >0){
// 如果存在数据,停止扫描
[self.session stopRunning];
[self.scanView stopAnimaion];
// AVMetadataMachineReadableCodeObject是AVMetadataObject的具体子类定义的特性检测一维或二维条形码。
// AVMetadataMachineReadableCodeObject代表一个单一的照片中发现机器可读的代码。这是一个不可变对象描述条码的特性和载荷。
// 在支持的平台上,AVCaptureMetadataOutput输出检测机器可读的代码对象的数组
AVMetadataMachineReadableCodeObject * metadataObject = [metadataObjects objectAtIndex:0];
// 获取扫描到的信息
NSString *stringValue = metadataObject.stringValue;
if (self.takeBlock) {
self.takeBlock(stringValue);
[self dismissViewControllerAnimated:YES completion:nil];
}
}
}
- (void)createScan{
//扫描区域
CGRect scanFrame = CGRectMake((SCREEN_WIDTH-ScanWidth)/2, _scanY, ScanWidth, ScanHeight);
// 创建view,用来辅助展示扫描的区域
self.scanView = [[QRCodeScanView alloc] initWithFrame:scanFrame];
[self.view addSubview:self.scanView];
//扫描区域外的背景
QRCodeBackgroundView *qrcodeBackgroundView = [[QRCodeBackgroundView alloc] initWithFrame:self.view.bounds];
qrcodeBackgroundView.scanFrame = scanFrame;
[self.view addSubview:qrcodeBackgroundView];
//提示文字
UILabel *label = [UILabel new];
label.text = @"将二维码/条形码放入框内,即可自动扫描";
label.textAlignment = NSTextAlignmentCenter;
label.font = [UIFont systemFontOfSize:15];
label.textColor = [UIColor colorWithRed:224/255.0 green:224/255.0 blue:224/255.0 alpha:1.0];
label.frame = CGRectMake(0, CGRectGetMaxY(self.scanView.frame)+10, SCREEN_WIDTH, 20);
[self.view addSubview:label];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self.session stopRunning];
[self.scanView stopAnimaion];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.session startRunning];
[self.scanView startAnimaion];
}
- (UIStatusBarStyle)preferredStatusBarStyle{
return UIStatusBarStyleLightContent;//白色
}
- (void)QRCodeImage:(NSData *)imageData{
CIDetector*detector = [CIDetector detectorOfType:CIDetectorTypeQRCode context:nil options:@{ CIDetectorAccuracy : CIDetectorAccuracyHigh }];
CIImage*ciImage = [CIImage imageWithData:imageData];
NSArray*features = [detector featuresInImage:ciImage];
if (features.count > 0) {
CIQRCodeFeature*feature = [features objectAtIndex:0];
NSString*scannedResult = feature.messageString;
if (self.takeBlock) {
self.takeBlock(scannedResult);
[self dismissViewControllerAnimated:YES completion:nil];
}
}else{
[self showFaild:@"未识别到二维码"];
}
}
@end
创建QRCodeScanView
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface QRCodeScanView : UIView
/**
* 开始动画
*/
-(void)startAnimaion;
/**
* 暂停动画
*/
-(void)stopAnimaion;
@end
#import "QRCodeScanView.h"
#import "DrawingImageTool.h"
#import "ColorHeader.h"
@interface QRCodeScanView()
//记录当前线条绘制的位置
@property (nonatomic,assign) CGPoint position;
// 定时器
@property (nonatomic,strong)NSTimer *timer;
//横线
@property (nonatomic,strong)UIImageView *lineImage;
@end
@implementation QRCodeScanView
- (instancetype)initWithFrame:(CGRect)frame{
if (self = [super initWithFrame:frame]) {
[self someInit];
}
return self;
}
- (void)someInit{
//边框图片
UIImageView *imageview = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
imageview.image = [self getFoucusImageSize:imageview.frame.size themeColor:ThemeColor lineWidth:10.0];
[self addSubview:imageview];
//横线
self.lineImage = [[UIImageView alloc] init];
self.lineImage.frame = CGRectMake(0, 0, self.frame.size.width, 4);
self.lineImage.image = [self getLineSize:self.lineImage.frame.size color:ThemeColor];
[self addSubview:self.lineImage];
self.timer = [NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:@selector(lineAnimation) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
}
- (void)lineAnimation{
CGPoint newPosition = self.position;
newPosition.y += 1;
//判断y到达底部,从新开始下降
if (newPosition.y > self.frame.size.height-self.lineImage.frame.size.height) {
newPosition.y = 0;
}
//重新赋值position
self.position = newPosition;
CGRect frame = self.lineImage.frame;
frame.origin.y = self.position.y;
[UIView animateWithDuration:0.01 animations:^{
self.lineImage.frame = frame;
}];
}
-(void)startAnimaion{
[self.timer setFireDate:[NSDate date]];
}
-(void)stopAnimaion{
[self.timer setFireDate:[NSDate distantFuture]];
}
- (void)dealloc{
[self.timer setFireDate:[NSDate distantFuture]];
self.timer = nil;
}
- (UIImage *)getFoucusImageSize:(CGSize)size themeColor:(UIColor *)color lineWidth:(CGFloat)lineWidth{
size = CGSizeMake(size.width-4, size.height-4);
UIGraphicsBeginImageContextWithOptions(size, NO, 0);
CGContextRef ctx = UIGraphicsGetCurrentContext();
//获取颜色RGB
CGFloat red = 0.0;
CGFloat green = 0.0;
CGFloat blue = 0.0;
CGFloat alpha = 0.0;
[color getRed:&red green:&green blue:&blue alpha:&alpha];
CGContextSetRGBStrokeColor(ctx,red,green,blue,1.0);//画笔线的颜色
CGContextSetLineWidth(ctx, lineWidth);//线的宽度
CGPoint sPoints[3];//坐标点
sPoints[0] = CGPointMake(0, size.height/5);
sPoints[1] = CGPointMake(0, 0);
sPoints[2] = CGPointMake(size.width/5, 0);
CGContextAddLines(ctx, sPoints, 3);//添加线
CGContextDrawPath(ctx, kCGPathStroke);
sPoints[0] = CGPointMake(0, size.height/5*4);
sPoints[1] = CGPointMake(0, size.height);
sPoints[2] = CGPointMake(size.width/5,size.height);
CGContextAddLines(ctx, sPoints, 3);//添加线
CGContextDrawPath(ctx, kCGPathStroke);
sPoints[0] = CGPointMake(size.width/5*4, size.height);
sPoints[1] = CGPointMake(size.width, size.height);
sPoints[2] = CGPointMake(size.width, size.height/5*4);
CGContextAddLines(ctx, sPoints, 3);//添加线
CGContextDrawPath(ctx, kCGPathStroke);
sPoints[0] = CGPointMake(size.width/5*4, 0);
sPoints[1] = CGPointMake(size.width, 0);
sPoints[2] = CGPointMake(size.width, size.height/5);
CGContextAddLines(ctx, sPoints, 3);//添加线
CGContextDrawPath(ctx, kCGPathStroke);
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
//关闭图形上下文
UIGraphicsEndImageContext();
return newImage;
}
- (UIImage *)getLineSize:(CGSize)size color:(UIColor *)color{
UIGraphicsBeginImageContextWithOptions(size, NO, 0);
CGContextRef context = UIGraphicsGetCurrentContext();
CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
//获取颜色RGB
CGFloat red = 0.0;
CGFloat green = 0.0;
CGFloat blue = 0.0;
CGFloat alpha = 0.0;
[color getRed:&red green:&green blue:&blue alpha:&alpha];
CGFloat colors[] =
{
red,green,blue, 0.00,
red,green,blue, alpha,
red,green,blue, 0.00,
};
CGGradientRef gradient = CGGradientCreateWithColorComponents
(rgb, colors, NULL, sizeof(colors)/(sizeof(colors[0])*4));//形成梯形,渐变的效果
CGColorSpaceRelease(rgb);
//画线形成一个矩形
//CGContextSaveGState与CGContextRestoreGState的作用
/*
CGContextSaveGState函数的作用是将当前图形状态推入堆栈。之后,您对图形状态所做的修改会影响随后的描画操作,但不影响存储在堆栈中的拷贝。在修改完成后,您可以通过CGContextRestoreGState函数把堆栈顶部的状态弹出,返回到之前的图形状态。这种推入和弹出的方式是回到之前图形状态的快速方法,避免逐个撤消所有的状态修改;这也是将某些状态(比如裁剪路径)恢复到原有设置的唯一方式。
*/
CGContextSaveGState(context);
CGContextMoveToPoint(context, 0, 0);
CGContextAddLineToPoint(context, 0, size.height);
CGContextAddLineToPoint(context, size.width, size.height);
CGContextAddLineToPoint(context, size.width, 0);
CGContextClip(context);//context裁剪路径,后续操作的路径
//CGContextDrawLinearGradient(CGContextRef context,CGGradientRef gradient, CGPoint startPoint, CGPoint endPoint,CGGradientDrawingOptions options)
//gradient渐变颜色,startPoint开始渐变的起始位置,endPoint结束坐标,options开始坐标之前or开始之后开始渐变
CGContextDrawLinearGradient(context, gradient,CGPointMake
(0,0) ,CGPointMake(size.width,0),
kCGGradientDrawsAfterEndLocation);
CGContextRestoreGState(context);// 恢复到之前的context
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
//关闭图形上下文
UIGraphicsEndImageContext();
return newImage;
}
QRCodeBackgroundView
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface QRCodeBackgroundView : UIView
/**
扫描区域大小
*/
@property(nonatomic,assign)CGRect scanFrame;
@end
#import "QRCodeBackgroundView.h"
@implementation QRCodeBackgroundView
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
// Drawing code
}
*/
- (instancetype)initWithFrame:(CGRect)frame{
if (self = [super initWithFrame:frame]) {
self.backgroundColor = [UIColor clearColor];
}
return self;
}
- (void)drawRect:(CGRect)rect{
CGContextRef context = UIGraphicsGetCurrentContext();
//填充区域颜色
[[UIColor colorWithRed:0 green:0 blue:0 alpha:0.5] set];
//扫码区域上面填充
CGRect notScanRect = CGRectMake(0, 0, self.frame.size.width, _scanFrame.origin.y);
CGContextFillRect(context, notScanRect);
//扫码区域左边填充
rect = CGRectMake(0, _scanFrame.origin.y, _scanFrame.origin.x,_scanFrame.size.height);
CGContextFillRect(context, rect);
//扫码区域右边填充
rect = CGRectMake(CGRectGetMaxX(_scanFrame), _scanFrame.origin.y, _scanFrame.origin.x,_scanFrame.size.height);
CGContextFillRect(context, rect);
//扫码区域下面填充
rect = CGRectMake(0, CGRectGetMaxY(_scanFrame), self.frame.size.width,self.frame.size.height - CGRectGetMaxY(_scanFrame));
CGContextFillRect(context, rect);
}
@end
生成二维码
+ (UIImage *)createQRCodeWithData:(NSString *)dataString imageSize:(CGFloat)size{
// 1. 创建一个二维码滤镜实例(CIFilter)
CIFilter *filter = [CIFilter filterWithName:@"CIQRCodeGenerator"];
// 滤镜恢复默认设置
[filter setDefaults];
// 2. 给滤镜添加数据
NSData *data = [dataString dataUsingEncoding:NSUTF8StringEncoding];
// 使用KVC的方式给filter赋值
[filter setValue:data forKeyPath:@"inputMessage"];
// 3. 生成二维码
CIImage *codeImage = [filter outputImage];
return [self QRCodeUIImageFormCIImage:codeImage withSize:size];
}
+ (UIImage *)QRCodeUIImageFormCIImage:(CIImage *)image withSize:(CGFloat)size{
CGRect extent = CGRectIntegral(image.extent);
CGFloat scale = MIN(size/CGRectGetWidth(extent), size/CGRectGetHeight(extent));
// 1.创建bitmap;
size_t width = CGRectGetWidth(extent) * scale;
size_t height = CGRectGetHeight(extent) * scale;
CGColorSpaceRef cs = CGColorSpaceCreateDeviceGray();
CGContextRef bitmapRef = CGBitmapContextCreate(nil, width, height, 8, 0, cs, (CGBitmapInfo)kCGImageAlphaNone);
CIContext *context = [CIContext contextWithOptions:nil];
CGImageRef bitmapImage = [context createCGImage:image fromRect:extent];
CGContextSetInterpolationQuality(bitmapRef, kCGInterpolationNone);
CGContextScaleCTM(bitmapRef, scale, scale);
CGContextDrawImage(bitmapRef, extent, bitmapImage);
// 2.保存bitmap到图片
CGImageRef scaledImage = CGBitmapContextCreateImage(bitmapRef);
CGContextRelease(bitmapRef);
CGImageRelease(bitmapImage);
UIImage *newCodeImage = [UIImage imageWithCGImage:scaledImage];
return newCodeImage;
}











网友评论