美文网首页
iOS 扫描二维码和生成二维码

iOS 扫描二维码和生成二维码

作者: Sh1mmer | 来源:发表于2019-07-05 15:57 被阅读0次

扫描二维码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;
}

相关文章

网友评论

      本文标题:iOS 扫描二维码和生成二维码

      本文链接:https://www.haomeiwen.com/subject/ondehctx.html