当前位置: 澳门新濠3559 > 编程 > 正文

二维码是app中非常常见的一个功能,下图显示付

时间:2019-12-27 17:08来源:编程
诸君大佬,本身现在在做一个app,各种页面都要用到二维码扫码,笔者想咨询下,作者是创制贰个二维码扫码分界面就可以,依然各样页面都要制造一个新的二维码扫描分界面呢,假设

诸君大佬,本身现在在做一个app,各种页面都要用到二维码扫码,笔者想咨询下,作者是创制贰个二维码扫码分界面就可以,依然各样页面都要制造一个新的二维码扫描分界面呢,假设用三个分界面就足以,怎么传扫描的值吗?

开篇

二维码在软件中的应用到底比较宽泛的,不可枚举的诸如生成二维码名片,扫码支付,扫码生成邀约码等生机勃勃层层操作。近来直接在圆满扫码部分的代码和页面逻辑,前几天有空就伙同整理一下100%工艺流程相关的东西吧。

  • 生成二维码厂商端的生成二维码供客人扫码付款生成自个儿的邀约二维码供客人扫码注册生成后的二维码状态刷新

  • 二维码扫描扫码页面包车型客车布局,二维码扫描框等处处页面包车型大巴拍卖调用相计算机扫描描二维码扫码后的判别来张开逻辑跳转

  • 长按识别相册二维码相册增添手势长按识别二维码

这里先放两张被围观的二维码分界面,分别是未被扫描和扫描中的页面布局。

始于状态 收款页面包车型地铁构造是规定的,满含付款成头像,姓名,付款情况等,会遵照页面所处的状态举行呈现。 下图浮现付款中的状态

给付中 二维码的变化代码:

 NSString *codeUrlString = [[[data objectForKey:@"data"] objectForKey:@"content"] objectForKey:@"url"]; erweima_id =[[[data objectForKey:@"data"] objectForKey:@"content"] objectForKey:@"erweima_id"]; ZXEncodeHints *hints = [ZXEncodeHints hints]; hints.encoding = NSUTF8StringEncoding;// 设置编码类型 hints.errorCorrectionLevel = [ZXQRCodeErrorCorrectionLevel errorCorrectionLevelH]; // 设置纠正级别,越高识别越快 ZXMultiFormatWriter *writer = [ZXMultiFormatWriter writer]; ZXBitMatrix* result = [writer encode:codeUrlString format:kBarcodeFormatQRCode width:500 height:500 hints:hints error:&error]; if  { CGImageRef image = [[ZXImage imageWithMatrix:result] cgimage]; self.qrCodeImageView.image=[UIImage imageWithCGImage:image]; } else { }

此处运用的ZXingObJC库生成的二维码,生成之后创立反应计时器实行二维码的幼功代谢

 [netRequestTimer invalidate]; netRequestTimer = [NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:@selector(ScanCodeStartNetworkRequest) userInfo:nil repeats:YES]; [netRequestTimer fire];

在ScanCodeStartNetworkRequest中,举办互连网请求,依据后台的归来的数量来展开二维码状态的剖断,看是高居未扫码,付款中,依然付款成功等情状,同不日常候调控分界面别的控件的展现和藏身。在付款成功后,遵照后台重回参数,显示自个儿提供的表彰或别的分界面。注意:在页面消失后要关门电磁打点计时器。

二维码扫描页面布局,分明他的分界面颜色,亮色线条的上线滚动动漫,透明部分的节制,边界铁黑框的绘图。当然也得以用第三方来安装这些分界面上的view然后扩充加载。

图片 1二维码扫描页

在二维码分界面中写贰个block来传递须要的参数,部分代码如下

#import <UIKit/UIKit.h>typedef void(^QRUrlBlock)(NSString *url,NSString* invite_code,BOOL isResist);@interface QRViewController : UIViewController<UIAlertViewDelegate>@property (nonatomic, copy) QRUrlBlock qrUrlBlock;@end

.m文件中调用相机,导入#import <AVFoundation/AVFoundation.h>固守AVCaptureMetadataOutputObjectsDelegate部分代码如下:

@interface QRViewController ()<AVCaptureMetadataOutputObjectsDelegate,QRViewDelegate>@property (strong, nonatomic) AVCaptureDevice * device;@property (strong, nonatomic) AVCaptureDeviceInput * input;@property (strong, nonatomic) AVCaptureMetadataOutput * output;@property (strong, nonatomic) AVCaptureSession * session;@property (strong, nonatomic) AVCaptureVideoPreviewLayer * preview;@end@implementation QRViewController- viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. self.view.backgroundColor = [UIColor whiteColor]; _device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; // Input _input = [AVCaptureDeviceInput deviceInputWithDevice:self.device error:nil]; // Output _output = [[AVCaptureMetadataOutput alloc]init]; [_output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()]; // Session _session = [[AVCaptureSession alloc]init]; [_session setSessionPreset:AVCaptureSessionPresetHigh]; if ([_session canAddInput:self.input]) { [_session addInput:self.input]; } if ([_session canAddOutput:self.output]) { [_session addOutput:self.output]; } if([[[UIDevice currentDevice] systemVersion] floatValue]>= 7.0) { //判断相机是否能够使用 AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]; if(status == AVAuthorizationStatusAuthorized) { // authorized _output.metadataObjectTypes =@[AVMetadataObjectTypeQRCode]; _preview =[AVCaptureVideoPreviewLayer layerWithSession:_session]; _preview.videoGravity =AVLayerVideoGravityResize; _preview.frame =self.view.layer.bounds; [self.view.layer insertSublayer:_preview atIndex:0]; [_session startRunning]; CGFloat displacementGap= 0.0; if (self.qr_type == QR_Type_AddFriend) { displacementGap = 60.0; } //此处创建之前 的设置扫面界面的View // CGRect screenRect = [UIScreen mainScreen].bounds; QRView *qrRectView = [[QRView alloc] initWithFrame:CGRectMake(0, -displacementGap, SCREEN_WIDTH, SCREEN_HEIGHT + displacementGap)]; qrRectView.transparentArea = CGSizeMake(KQRWIDTH, KQRWIDTH); qrRectView.backgroundColor = [UIColor clearColor]; // qrRectView.center = CGPointMake(self.view.frame.size.width / 2, self.view.frame.size.height / 2); qrRectView.delegate = self; self.view.backgroundColor = qrRectView.backgroundColor; [self.view addSubview:qrRectView]; // UIButton *pop = [UIButton buttonWithType:UIButtonTypeCustom]; // pop.frame = CGRectMake(20, 20, 50, 50); // [pop setTitle:@"返回" forState:UIControlStateNormal]; // [pop addTarget:self action:@selector forControlEvents:UIControlEventTouchUpInside]; // [self.view addSubview:pop]; //修正扫描区域 CGFloat screenHeight = self.view.frame.size.height; CGFloat screenWidth = self.view.frame.size.width; CGRect cropRect = CGRectMake((screenWidth - qrRectView.transparentArea.width) / 2, (screenHeight - qrRectView.transparentArea.height) / 2, qrRectView.transparentArea.width, qrRectView.transparentArea.height); [_output setRectOfInterest:CGRectMake(cropRect.origin.y / screenHeight, cropRect.origin.x / screenWidth, cropRect.size.height / screenHeight, cropRect.size.width / screenWidth)]; } else if(status == AVAuthorizationStatusDenied){ // denied UIAlertController * alertcontrol=[UIAlertController alertControllerWithTitle:@"未获得授权使用摄像头" message:@"请在ios“设置”-“隐私”-“相机”中打开!" preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction * action=[UIAlertAction actionWithTitle:@"知道了" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { [self.navigationController popViewControllerAnimated:YES]; }]; [alertcontrol addAction:action]; [self presentViewController:alertcontrol animated:YES completion:nil]; return ; } else if(status == AVAuthorizationStatusRestricted){ // restricted return; } else if(status == AVAuthorizationStatusNotDetermined){ // not determined [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) { if{ } else { return; } }]; } }

在AVCaptureMetadataOutputObjectsDelegate合同章程中依据二维码的音讯来实行赋值和页面跳转,部分代码如下:

#pragma mark AVCaptureMetadataOutputObjectsDelegate- captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection{ NSString *stringValue; if ([metadataObjects count] >0) { //停止扫描 [_session stopRunning]; AVMetadataMachineReadableCodeObject * metadataObject = [metadataObjects objectAtIndex:0]; stringValue = metadataObject.stringValue; }//根据条件进行判断 举例如下 : if([stringValue rangeOfString:@"erweimahzb_id"].location != NSNotFound) { /** * 线下扫码支付 */ [self pop:nil]; NSDictionary* dict=[self dictionaryFromQuery:[[stringValue componentsSeparatedByString:@"?"] objectAtIndex:1] usingEncoding:NSUTF8StringEncoding]; if ([dict objectForKey:@"user_id"] && [dict objectForKey:@"erweimahzb_id"]) { if (self.qrUrlBlock) {//对block处理 self.qrUrlBlock([dict objectForKey:@"user_id"],[dict objectForKey:@"erweimahzb_id"],0); } }else { [[[UIAlertView alloc] initWithTitle:@"温馨提示" message:@"扫码异常,请重新扫码!" delegate:nil cancelButtonTitle:@"确定" otherButtonTitles:Nil, nil] show]; }}

透过以上思路我们设置实现二维码扫描页面后,在点击对应按键弹出二维码扫描的点击事件里,实现block方法的达成点击事件在早已张开相机的前提下代码管理如下:

//加载二维码扫描的Controller QRViewController *qrVC = [[QRViewController alloc] init]; __block ProfileViewController *weakSelf = self; qrVC.qrUrlBlock=^(NSString* parent_id,NSString* invite_code,BOOL isResist){ weakSelf.navigationController.navigationBar.hidden = YES; if (!isResist) { //如果不是扫描邀请注册在这里处理数据,并实现跳转逻辑 }else{ //如果是扫码注册界面在此处处理数据,并跳转注册界面 }

上述代码只是做一个精短的举例,列出了贯彻思路,具体的逻辑依附自身项指标区别意况可做越来越的优化。

给选拔的图形增加长按手势,在手势的丰裕事件中打开推断,代码如下

if(gesture.state==UIGestureRecognizerStateBegan){ UIImageView*tempImageView=(UIImageView*)gesture.view; if(tempImageView.image){ CIDetector*detector = [CIDetector detectorOfType:CIDetectorTypeQRCode context:nil options:@{ CIDetectorAccuracy : CIDetectorAccuracyHigh }]; NSArray *features = [detector featuresInImage:[CIImage imageWithCGImage:tempImageView.image.CGImage]]; //扫描结果 CIQRCodeFeature *feature = [features objectAtIndex:0]; NSLog(@"输出扫描内容:%@",feature.messageString); }else if (gesture.state==UIGestureRecognizerStateEnded){ }

记录下今后的某些设法,今后忘记的时候低价查阅。

saomashengqi.png

后记

二维码相比常用到的有个别功能做了二个简洁明了的介绍,或者有点说的不是很确切,希望开掘的朋侪积极提议。在触发项目标还要也融洽依照模块总结计算了瞬间,即便还未有具体的代码,但是最首要的逻辑思路和关键部分的代码已经有了,希望做个备忘,同不时候对大家具备利于。(PS:简书的鸡汤文如故照样的多,种种xxx看本身就够了,我用XXX完毕了XXX ,你XXX为何XXX,真是够够的,希望团结能百折不挠下去,足履实地的写点才能文章积存下自身吗!加油!)

二维码是app中国和南美洲常广阔的贰个成效,且以往数不尽app的骨干作用都亟待用到二维码(支付宝、Wechat扫码支付),由此花点时间将相关的知识点纪录下来。

举目四望二维码(包罗读取和平解决码)

围观二维码OC的开源库有ZBar和ZXingiOS7以后iOS具有原生的扫码效率。
我在iPhone项目扫码神奇中央银行使的就是上面第二种艺术,大家能够到应用软件Store寻觅扫码神奇二维码是app中非常常见的一个功能,下图显示付款中的状态。,下载体验一下(记得给个美评呦)。扫码神奇下载链接:
https://itunes.apple.com/cn/app/id1074173840

icon108.png

好了上边是有板有眼实现介绍:

规律简要介绍

二维码(2-dimensional bar code)是用某种特定的几何图形按自然规律在平面(二维方向上)布满的黑白相间的图纸记录数据符号音讯的;在代码编写制定上抢眼地运用构成计算机内部逻辑根底的“0”、“1”比特流的定义,使用几何个与二进制相对应的几何形体来代表文字数值新闻,通过图象输入设备或光电扫描设备自动识读以落实音信自动管理:它具备条码技艺的有的共性:种种码制有其特定的字符集;每一种字符据有一定的肥瘦;具有自然的校验作用等。同不常间还兼具对分歧行的音信自动识别功用、及管理图片旋调换化点。近些日子国内左近利用的二维码为来自东瀛的全速响应码(QRCode)。

iOS7早前,app中关键利用ZXing/ZBar那八个开源库来落到实处二维码相关成效。iOS7过后,AVFundation帮衬了二维码,且在扫描灵敏度和性质上的话都优于第三方框架。本文首要针对利用原生框架达成二维码相关效率所需知识点实行重新整建。即使功能并不复杂,但在优化及整合治理方面可能费用了重重的时刻。

ZXing

现在OC本子现已终止维护,Java版本还在保证,github链接:https://github.com/zxing/zxing,今后也超少有人利用,在唐巧的一篇12年http://blog.devtang.com/blog/2012/12/23/use-zxing-library/小说中有至于ZXing的配备进度,不过好像他长时间未有改革了。ZXing相对于ZBar的好处是读取和扫码速度快,不过结合到Xcode品种中比较痛苦。

急需落到实处的功力
  • 二维码扫描
  • 增添扫描音响效果
  • 加上闪光灯按键
  • 二维码生成/自定义颜色
  • 为二维码增多Logo
  • 二维码识别(app内、相册中图纸识别)

为了便于自定义扫码分界面包车型大巴UI,大家将成效部分尽可能从调整器抽离封装成叁个工具类JYQRCodeTool

ZBar

ZBargithub地址:https://github.com/bmorton/ZBarSDK ,在github上下载的.a文件不支持64位,好像也可以有好今年不曾更新了,不过自个儿在网络找到了支撑60位的.a文本,下载链接:https://markobl.com/2015/03/27/zbar-sdk-64-bit-for-iphone-6-and-ios-8-download/ ,上边说一下ZBar的合併步骤:

  • 从github左右载源文件,解压后如下图,把Headers文件夹拖入到品种中。
  • 从64位.a文件下载扶持陆拾位的.a文件,加多到品种中
  • 在新工程中程导弹入以下框架:AVFoundation.framework、CoreMedia.framework、CoreVideo.framework、QuartzCore.framework、libiconv.dylib
  • 在急需运用的页面.h文本中援引头文件#import "ZBarSDK.h"

.m文本中在点击起头扫描的开关中落实:

ZBarReaderViewController *reader = [ZBarReaderViewController new];  
  reader.readerDelegate = self;  
reader.supportedOrientationsMask = ZBarOrientationMaskAll;  

  ZBarImageScanner *scanner = reader.scanner;  
  [scanner setSymbology: ZBAR_I25  
                 config: ZBAR_CFG_ENABLE  
                     to: 0];  
  [self presentViewController:reader  
                     animated:YES  
                   completion:^{  
  }];

还要落到实处代理

- (void) imagePickerController: (UIImagePickerController*) reader didFinishPickingMediaWithInfo: (NSDictionary*) info{
    id<NSFastEnumeration> results = [info objectForKey: ZBarReaderControllerResults];  
    ZBarSymbol *symbol = nil;  
    for(symbol in results)  
         break;      
    [self dismissViewControllerAnimated:YES  
                           completion:^{  
                           }];  
    NSString *code = [NSString stringWithString:symbol.data];  

}

ZBar有一个破绽,因为她是静态文件,所以自定义相机相比不方便,只能进展简要的改造,笔者尝试完结Wechat扫码效果未有兑现。

二维码扫描(iOS7.0之上可用)

内需动用的类:

#import <AVFoundation/AVFoundation.h>

@interface JYQRCodeTool () <AVCaptureMetadataOutputObjectsDelegate>

/* 输入设备 */
@property(nonatomic,strong)AVCaptureDevice            *jyDevice;
/* 输入数据源 */
@property(nonatomic,strong)AVCaptureDeviceInput       *jyInput;
/* 输出数据源 */
@property(nonatomic,strong)AVCaptureMetadataOutput    *jyOutput;
/* 会话层 中间桥梁 负责把捕获的音视频数据输出到输出设备中 */
@property(nonatomic,strong)AVCaptureSession           *jySession;
/* 相机拍摄预览图层 */
@property(nonatomic,strong)AVCaptureVideoPreviewLayer *jyPreview;

@end

对此没有须要自定义的UI(如增加遮罩,相机预览层),我们也把它们封装到了工具类中,由于对UI进行操作需求获得调节器,大家在抬高二个性质:

#import <UIKit/UIKit.h>
//这里使用weak,防止循环引用
@property(nonatomic,weak)UIViewController *bindVC;

增进一个早先化方法,在初叶化时绑定扫码分界面的调整器:

.h

#import <Foundation/Foundation.h>

@interface JYQRCodeTool : NSObject

/**
 * 初始化二维码扫描工具
 *
 * @param controller 扫描界面对应的控制器
 **/
+ (instancetype)toolsWithBindingController:(UIViewController *)controller;

@end

.m

+ (instancetype)toolsWithBindingController:(UIViewController *)controller
{
    return [[self alloc] initWithBindingController:controller];
}

- (instancetype)initWithBindingController:(UIViewController *)controller
{
    if (self = [super init]) {
        if (_bindVC != controller) {
            _bindVC = controller;
        }
    }

    return self;
}

接下去增添三个格局,开头化扫码功效:

.h

/**
 * 初始化扫描二维码功能,自动确定扫描范围,范围外的部分加遮罩
 *
 * @param rect 扫描范围
 **/
- (void)jy_setUpCaptureWithRect:(CGRect)rect;


/**
 * 初始化扫描二维码功能,添加完成后回调
 *
 * @param rect       扫描范围
 * @param successCB  完成后回调
 **/
- (void)jy_setUpCaptureWithRect:(CGRect)rect success:(void(^)())successCB;

.m

-(void)jy_setUpCaptureWithRect:(CGRect)rect
{
    return [self jy_setUpCaptureWithRect:rect success:nil];
}

- (void)jy_setUpCaptureWithRect:(CGRect)rect success:(void(^)())successCB
{
    //添加遮罩
    [self addCropRect:rect];

    CGSize cSize = [UIScreen mainScreen].bounds.size;
    //计算rectOfInterest 注意x,y交换位置
    CGRect rectOfInterest = CGRectMake(rect.origin.y/cSize.height, rect.origin.x/cSize.width, rect.size.height/cSize.height,rect.size.width/cSize.width);

    //判断是否可以调用相机
    if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera])
    {
        [self setUpCaptureWithRect:rectOfInterest succees:successCB];
    }
    else{
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"设备不支持该功能" message:nil delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil];
        [alert show];
    }
}

大家运用CAShapeLayer来为扫码范围外的某些增加遮罩,具体的措施正是在layer上画七个矩形,三个rect为扫码范围,另八个rect为显示屏尺寸,然后将三个矩形之间的片段填充为驼灰,并安装折射率,那样扫码范围外的遮罩就做好了:

//添加遮罩
- (void)addCropRect:(CGRect)cropRect{

    CAShapeLayer *cropLayer = [[CAShapeLayer alloc] init];
    CGMutablePathRef path = CGPathCreateMutable();
    CGPathAddRect(path, nil, cropRect);
    CGPathAddRect(path, nil, _bindVC.view.bounds);

    [cropLayer setFillRule:kCAFillRuleEvenOdd];
    [cropLayer setPath:path];
    [cropLayer setFillColor:[UIColor blackColor].CGColor];
    [cropLayer setOpacity:0.6];

    [_bindVC.view.layer addSublayer:cropLayer];
}

接下去是开首化扫码效用的具体贯彻:

- (void)setUpCaptureWithRect:(CGRect)rectOfInterest succees:(void(^)())successCB
{
    AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
    //判断相机权限
    if (authStatus == AVAuthorizationStatusDenied) {
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"相机权限已关闭" message:@"请在设置->隐私->相机中,允许app访问相机。" delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil];
        [alert show];
    }
    else{
        dispatch_async(dispatch_get_main_queue(), ^{
            //初始化输入流,设备为相机
            _jyDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
            _jyInput = [AVCaptureDeviceInput deviceInputWithDevice:_jyDevice error:nil];

            //初始化输出流,设置代理,在主线程里刷新
            _jyOutput = [[AVCaptureMetadataOutput alloc] init];
            [_jyOutput setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];

            //初始化会话层,添加输入/输出流
            _jySession = [[AVCaptureSession alloc] init];
            [_jySession setSessionPreset:([UIScreen mainScreen].bounds.size.height<500)?AVCaptureSessionPreset640x480:AVCaptureSessionPresetHigh];
            [_jySession addInput:_jyInput];
            [_jySession addOutput:_jyOutput];

            //设置输出对象类型为QRCode,设置坐标
            _jyOutput.metadataObjectTypes = @[AVMetadataObjectTypeQRCode];
            _jyOutput.rectOfInterest = rectOfInterest;

            //初始化相机预览层
            _jyPreview = [AVCaptureVideoPreviewLayer layerWithSession:_jySession];
            _jyPreview.videoGravity = AVLayerVideoGravityResizeAspectFill;
            _jyPreview.frame = [[UIScreen mainScreen] bounds];
            [_bindVC.view.layer insertSublayer:_jyPreview atIndex:0];

            //开启会话层,启用扫码功能
            [_jySession startRunning];

            //初始化完成回调,可在此移除UI上的loading界面
            if (successCB) {
                successCB();
            }
        });
    }
}

此处有多少个注意点说Bellamy(Bellamy卡塔尔国下,首先是rectOfInterest属性,那天个性决定扫码范围,默许值为CGRectMake(0, 0, 1, 1卡塔尔(英语:State of Qatar);即全屏扫描,因为扫码暗中同意是横屏,所以该值坐标系原点在右上角,与UIView的坐标系区别,x,y坐标要求交流,且取值是遵照录制头分辨率来取的百分比,并不是显示屏的宽高比例。由于4英寸以上机型宽高比与摄像头保持生龙活虎致,所以大家针对3.5英寸机型做一下适配,那样使用显示器宽高比也不会有不小抽样误差。

[_jySession setSessionPreset:([UIScreen mainScreen].bounds.size.height<500)?AVCaptureSessionPreset640x480:AVCaptureSessionPresetHigh];

然后调用该格局前大家对坐标进行了改动处理:

CGSize cSize = [UIScreen mainScreen].bounds.size;
//计算rectOfInterest 注意x,y交换位置
CGRect rectOfInterest = CGRectMake(rect.origin.y/cSize.height, rect.origin.x/cSize.width, rect.size.height/cSize.height,rect.size.width/cSize.width);

由于初阶化那部分可比耗费时间,测量试验中明显感到到到了卡顿感严重,所以自个儿刚开首想到用八线程来管理,可是相机预览层又对UI实践了操作,不能够放进子线程。最终我们利用了这种方式:

dispatch_async(dispatch_get_main_queue(), ^{

}

在主线程中异步调用主队列加多职务,不开辟新线程,不会堵塞主线程,增加的职分会在主线程中的职责试行完后再起来实践。那样既不会引致卡顿又有啥不可平常带头化,只是要多花一小点的光阴,因为主线程串行,不可能到位切换页面包车型大巴同时打开早先化,只好等到页面加载完再拓宽发轫化,这里能够在UI上做三个loading分界面,初步化完结后在block中移除loading以优化体验。关于这里想详细精晓的同校能够看作者的另风流浪漫篇随笔 iOS 八线程之GCD相关知识点梳理

为了能够在扫码分界面进行扫码成功后的数码解析,我们还亟需传递AVCaptureMetadataOutput的delegate。在头文件中声明一(Beingmate卡塔尔(英语:State of Qatar)个体协会谈商讨:

.h

@protocol JYQRCodeDelegate <NSObject>

/**
 * 扫描成功回调
 **/
- (void)jy_willGetOutputMataDataObject;

/**
 * 数据处理完毕回调
 *
 * @param outPutString 返回的数据
 **/
- (void)jy_didGetOutputMataDataObjectToString:(NSString *)outPutString;

@end

@property(nonatomic,weak) id <JYQRCodeDelegate> delegate;

.m

#pragma mark - Delegate

-(void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection
{
    if (metadataObjects.count > 0) {
        //关闭会话
        [_jySession stopRunning];
        //播放扫描音效
        [self playScanSoundsWithName:_scanVoiceName];

        if ([_delegate respondsToSelector:@selector(jy_willGetOutputMataDataObject)]) {
            //扫描成功后的回调,在这里显示loading
            [_delegate jy_willGetOutputMataDataObject];
        }

        dispatch_async(dispatch_get_global_queue(0, 0), ^{

            //将数据转为NSString
            AVMetadataMachineReadableCodeObject *readableObj = metadataObjects.firstObject;
            NSString *outPutString = readableObj.stringValue;

            dispatch_async(dispatch_get_main_queue(), ^{
                if ([_delegate respondsToSelector:@selector(jy_didGetOutputMataDataObjectToString:)]) {
                    //获得数据的回调,在这里自行确定解析逻辑
                    [_delegate jy_didGetOutputMataDataObjectToString:outPutString];
                }
            });
        });
    }
}

末尾增添七个艺术,便于外面启用/禁止使用扫码效用:

.h

/**
 * 启用扫描功能
 **/
- (void)jy_startScaning;


/**
 * 禁用扫描功能
 **/
- (void)jy_stopScaning;

.m

- (void)jy_startScaning;
{
    if (_jySession && !_jySession.isRunning) {
        [_jySession startRunning];
    }
}

-(void)jy_stopScaning
{
    if (_jySession && _jySession.isRunning) {
        [_jySession stopRunning];
    }
}

到了那边,扫码作用部分就是封装好了。接下来制造贰个扫码视图调整器来使用:

#import "JYQRScanController.h"
#import "JYQRCodeTool.h"

@interface JYQRScanController () <JYQRCodeDelegate>

@property(nonatomic,strong)JYQRCodeTool *jyQRTool;

@end

//LazyLoad
-(JYQRCodeTool *)jyQRTool
{
    if (!_jyQRTool) {
        _jyQRTool = [JYQRCodeTool toolsWithBindingController:self];
        _jyQRTool.delegate = self;
    }

    return _jyQRTool;
}

- (void)viewDidLoad {
    [super viewDidLoad];

    self.view.backgroundColor = [UIColor blackColor];
//    self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"相册" style:UIBarButtonItemStylePlain target:self action:@selector(openPhotoLibrary)];
    self.navigationItem.title = @"扫一扫";

    CGSize cSize = [UIScreen mainScreen].bounds.size;
    //自定义扫描范围
    CGSize scanSize = CGSizeMake(cSize.width * 2.8/4, cSize.width * 2.8/4);
    CGRect scanRect = CGRectMake((cSize.width - scanSize.width) / 2, (cSize.height - scanSize.height) / 2, scanSize.width, scanSize.height);

    //初始化扫码功能
    [self.jyQRTool jy_setUpCaptureWithRect:scanRect];
}

#pragma mark - Delegate

-(void)jy_willGetOutputMataDataObject
{

}

-(void)jy_didGetOutputMataDataObjectToString:(NSString *)outPutString
{
    //对扫描获得的数据进行处理
}

iOS10个中须要对隐秘权限进行设置,由于大家用到了相机(相册、迈克风等设施同理),须要在info.plist中加多对应字段,否则程序运转会报错:

图片 2

QQ20170630-170907@2x.png

运营一下,效果如图:

图片 3

扫一扫

iOS原生扫码

iOS7现在应用软件le有了投机原生的扫码接口,我本来要勇往直前地采用接受原生的了,下边是本身在品种中的部分代码:
在.m文件中:

#import <AVFoundation/AVFoundation.h>
@interface SysScannerController ()<AVCaptureMetadataOutputObjectsDelegate,UIImagePickerControllerDelegate,UINavigationControllerDelegate>
@property (nonatomic, strong) AVCaptureSession *session;//输入输出的中间桥梁
@property (strong,nonatomic)AVCaptureDevice *device;
@property (strong,nonatomic)AVCaptureDeviceInput *input;
@property (strong,nonatomic)AVCaptureMetadataOutput *output;
@property (strong,nonatomic)AVCaptureVideoPreviewLayer *preview;
@end

懒加载:

#pragma mark - Setter & Getter
-(UIView *)line{
    if (_line == nil) {
        _line = [[UIView alloc] initWithFrame:CGRectMake((ScreenFrameWith-180)/2, ScreenFrameHeight/3.f, 180, 4)];
        _line.backgroundColor=[UIColor whiteColor];
        [self.view addSubview:_line];
    }
    return _line;
}

-(AVCaptureDevice *)device{
    if (_device == nil) {
        _device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
    }
    return _device;
}
-(AVCaptureDeviceInput *)input{
    if (_input == nil) {
        _input = [AVCaptureDeviceInput deviceInputWithDevice:self.device error:nil];
    }
    return _input;
}
-(AVCaptureMetadataOutput *)output{
    if (_output == nil) {
        _output = [[AVCaptureMetadataOutput alloc]init];
        [_output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
        //限制扫描区域(上左下右)
        [ _output setRectOfInterest : CGRectMake (1/6.f,1/6.f,4/6.f,4/6.f)];
    }
    return _output;
}

- (AVCaptureSession *)session{
    if (_session == nil) {
        // Session
        _session = [[AVCaptureSession alloc]init];
        [_session setSessionPreset:AVCaptureSessionPresetHigh];
        if ([_session canAddInput:self.input])
        {
            [_session addInput:self.input];
        }

        if ([_session canAddOutput:self.output])
        {
            [_session addOutput:self.output];
        }
    }
    return _session;
}

-(AVCaptureVideoPreviewLayer *)preview{
    if (_preview == nil) {
        _preview =[AVCaptureVideoPreviewLayer layerWithSession:self.session];

    }
    return _preview;
}

-viewDidLoad中调用下边函数:

- (void)setupCamera
{
    //1.
    if(self.device == nil){
        [self showAlertTipWithTitle:@"未检测到相机" andMessage:@"请检查相机设备是否正常"];
        return ;
    }
    // 2.添加预览图层
    [self.view.layer insertSublayer:self.preview atIndex:0];
    self.preview.frame = self.view.bounds;
    // 3.设置输出能够解析的数据类型
    // 注意点: 设置数据类型一定要在输出对象添加到会话之后才能设置
    self.output.metadataObjectTypes = self.output.availableMetadataObjectTypes;
    // 4.设置监听监听输出解析到的数据
    [self.output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
    // 5.开始扫描
    [self.session startRunning];

}

实现AVCaptureMetadataOutputObjectsDelegate 代理:

#pragma mark AVCaptureMetadataOutputObjectsDelegate

- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection
{

    if ([metadataObjects count] >0)
    {
        AVMetadataMachineReadableCodeObject * metadataObject = [metadataObjects objectAtIndex:0];

        if ([metadataObject isKindOfClass:[AVMetadataMachineReadableCodeObject class]]) {
            NSString *stringValue = [metadataObject stringValue];
            if (stringValue != nil) {
                [self.session stopRunning];
                //扫描结果
                self.scannedResult=stringValue;
            }

        }
    }
}

iOS原生的扫码分界面你能够率性的自定义了,和自定义相机同样,这里就不做牵线了。

自定义扫码界面

我们用一句代码开启了扫码功用,接下去大家得以对扫码框部分来开展特性化设置,平日我们看看的app中扫码框的四角有标识鲜明约束,中间有扫描线动漫,上面就来回顾的装点一下。

创立八个JYScanRectView,世袭于UIView:

-(instancetype)initWithFrame:(CGRect)frame
{
    if (self = [super initWithFrame:frame]) {
        self.layer.borderColor = [UIColor darkGrayColor].CGColor;
        self.layer.borderWidth = 1.0;

        [self customScanCorners];
        [self customScanLine];
        [self customLoadingView];
    }

    return self;
}

能够选取CAShapeLayer+UIBezierPath加多四角标志:

//添加四角标识
-(void)customScanCorners
{
    CGFloat cWidth = self.bounds.size.width;
    CGFloat cHeight = self.bounds.size.height;

    NSArray *pointArray = @[@{@"top":[NSValue valueWithCGPoint:CGPointMake(2, 20)],
                              @"mid":[NSValue valueWithCGPoint:CGPointMake(2, 2)],
                              @"end":[NSValue valueWithCGPoint:CGPointMake(20, 2)]},

                            @{@"top":[NSValue valueWithCGPoint:CGPointMake(cWidth - 20, 2)],
                              @"mid":[NSValue valueWithCGPoint:CGPointMake(cWidth - 2, 2)],
                              @"end":[NSValue valueWithCGPoint:CGPointMake(cWidth - 2, 20)]},

                            @{@"top":[NSValue valueWithCGPoint:CGPointMake(cWidth - 2, cHeight - 20)],
                              @"mid":[NSValue valueWithCGPoint:CGPointMake(cWidth - 2, cHeight - 2)],
                              @"end":[NSValue valueWithCGPoint:CGPointMake(cWidth - 20, cHeight - 2)]},

                            @{@"top":[NSValue valueWithCGPoint:CGPointMake(20, cHeight - 2)],
                              @"mid":[NSValue valueWithCGPoint:CGPointMake(2, cHeight - 2)],
                              @"end":[NSValue valueWithCGPoint:CGPointMake(2, cHeight - 20)]},];


    for (NSInteger i = 0; i < pointArray.count; i++) {

        CAShapeLayer *shapeLayer = [CAShapeLayer layer];
        shapeLayer.lineWidth = 3.0;
        shapeLayer.strokeColor = [UIColor greenColor].CGColor;
        shapeLayer.fillColor = [UIColor clearColor].CGColor;

        UIBezierPath *path = [UIBezierPath bezierPath];
        [path moveToPoint:[pointArray[i][@"top"] CGPointValue]];
        [path addLineToPoint:[pointArray[i][@"mid"] CGPointValue]];
        [path addLineToPoint:[pointArray[i][@"end"] CGPointValue]];

        shapeLayer.path = path.CGPath;

        [self.layer addSublayer:shapeLayer];
    }

}

用三个UIView作为扫描线,动漫部分选用CABaseAnimation(假设有扫描线图片,能够用UIImageView):

@property(nonatomic,strong)UIView *scanView;

//添加扫描线
-(void)customScanLine
{
    CGFloat cWidth = self.bounds.size.width;

    _scanView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, cWidth, 2)];
    _scanView.backgroundColor = [UIColor greenColor];
    _scanView.alpha = 0.0;

    [self addSubview:_scanView];
}

-(void)startScanAnim
{
    if (![_scanView.layer animationForKey:@"ScanAnim"]) {

        _scanView.alpha = 1.0;
        CGFloat cHeight = self.bounds.size.height;

        CABasicAnimation *moveAnimation = [CABasicAnimation animationWithKeyPath:@"transform.translation.y"];
        moveAnimation.fromValue = @0;
        moveAnimation.toValue = [NSNumber numberWithFloat:cHeight - 2];
        moveAnimation.duration = 2.4;
        moveAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];

        CABasicAnimation *fadeInAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"];
        fadeInAnimation.fromValue = @0;
        fadeInAnimation.toValue = @1;
        fadeInAnimation.duration = 0.6;

        CABasicAnimation *fadeOutAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"];
        fadeOutAnimation.fromValue = @1;
        fadeOutAnimation.toValue = @0;
        fadeOutAnimation.duration = 0.6;
        fadeOutAnimation.beginTime = 1.8;

        CAAnimationGroup *group = [CAAnimationGroup animation];
        group.animations = @[moveAnimation,fadeInAnimation,fadeOutAnimation];
        group.duration = 2.4;
        group.repeatCount = HUGE_VALF;
        group.removedOnCompletion = NO;
        group.fillMode = kCAFillModeForwards;

        [_scanView.layer addAnimation:group forKey:@"ScanAnim"];
    }
}


-(void)stopScanAnim
{
    if ([_scanView.layer animationForKey:@"ScanAnim"]) {
        [_scanView.layer removeAllAnimations];
        _scanView.alpha = 0.0;
    }
}

至于CoreAnimation这里不作详细介绍,感兴趣的同窗能够运动 CAAnimation大旨动漫

添加loading视图:

@property(nonatomic,strong)UIView *loadingView;
@property(nonatomic,strong)UIActivityIndicatorView *actView;
@property(nonatomic,strong)UILabel *loadingLabel;

//添加loading视图
-(void)customLoadingView
{
    _loadingView = [[UIView alloc] initWithFrame:self.bounds];
    _loadingView.backgroundColor = [UIColor blackColor];
    [self addSubview:_loadingView];

    _actView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
    _actView.center = CGPointMake(self.bounds.size.width / 2, self.bounds.size.height / 2);
    [_actView startAnimating];
    [self addSubview:_actView];

    _loadingLabel = [[UILabel alloc] initWithFrame:CGRectMake(20, self.bounds.size.height / 2 + 25, self.bounds.size.width - 40, 30)];
    _loadingLabel.textColor = [UIColor whiteColor];
    _loadingLabel.font = [UIFont systemFontOfSize:15];
    _loadingLabel.textAlignment = NSTextAlignmentCenter;
    _loadingLabel.text = @"处理中,请稍候";
    [self addSubview:_loadingLabel];
}

在.h中声美素佳儿(Friso卡塔尔本性质用来支配扫描线动漫以至loading视图的显得:

#import <UIKit/UIKit.h>

@interface JYScanRectView : UIView

@property(nonatomic,getter=isLoading)BOOL loading;

@end

#pragma mark - Override Setter & Getters

-(void)setLoading:(BOOL)loading
{
    if (_loading != loading) {
        _loading = loading;

        _loadingView.alpha = _loading?0.6:0.0;
        _actView.alpha = _loading;
        _loadingLabel.alpha = _loading;

        if (!loading) {
            [self startScanAnim];
        }
        else{
            [self stopScanAnim];
        }
    }
}

回去调节器,创设一个扫描框:

@property(nonatomic,strong)JYScanRectView *jyScanRectView;
@property(nonatomic,strong)UILabel *scanLabel;

//创建扫描框
-(void)setUpRectViewWithRect:(CGRect)scanRect
{
    _jyScanRectView = [[JYScanRectView alloc] initWithFrame:scanRect];
    [self.view addSubview:_jyScanRectView];

    _scanLabel = [[UILabel alloc] initWithFrame:CGRectMake(_jyScanRectView.frame.origin.x, _jyScanRectView.frame.origin.y + _jyScanRectView.frame.size.height + 5, _jyScanRectView.frame.size.width, 30)];
    _scanLabel.textColor = [UIColor whiteColor];
    _scanLabel.font = [UIFont systemFontOfSize:13];
    _scanLabel.textAlignment = NSTextAlignmentCenter;
    _scanLabel.text = @"将二维码/条码放入框内,即可自动扫描";
    [self.view addSubview:_scanLabel];
}

在-viewDidLoad中,那样举办设置:

- (void)viewDidLoad {
    [super viewDidLoad];

    self.view.backgroundColor = [UIColor blackColor];
    //self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"相册" style:UIBarButtonItemStylePlain target:self action:@selector(openPhotoLibrary)];
    self.navigationItem.title = @"扫一扫";

    CGSize cSize = [UIScreen mainScreen].bounds.size;

    CGSize scanSize = CGSizeMake(cSize.width * 2.8/4, cSize.width * 2.8/4);
    CGRect scanRect = CGRectMake((cSize.width - scanSize.width) / 2, (cSize.height - scanSize.height) / 2, scanSize.width, scanSize.height);

    [self setUpRectViewWithRect:scanRect];

    _jyScanRectView.loading = YES;
    [self.jyQRTool jy_setUpCaptureWithRect:scanRect success:^{
        _jyScanRectView.loading = NO;
    }];
}

运作一下,效果如图:

图片 4

扫一扫

相像的话,二维码扫描成功博得的是多个字符串类型的UPAJEROL,大家大概的做一下剖判:

#pragma mark - Delegate
-(void)jy_didGetOutputMataDataObjectToString:(NSString *)outPutString
{
    //对扫描获得的数据进行处理
    [self visitWebViewWithUrl:outPutString];
}

-(void)visitWebViewWithUrl:(NSString *)url
{
    WebViewController *webVC = [[WebViewController alloc] init];
    webVC.urlStr = url;
    [self.navigationController pushViewController:webVC animated:YES];
}

优化一下扫描成功后等候数据处理的分界面,声诺优能(Nutrilon卡塔尔国个属性用来支配扫码和Loading界面包车型地铁开关:

@property(nonatomic,getter=isScanActive)BOOL scanActive;

#pragma mark - Override Setter & Getters
//启用/禁用扫描
-(void)setScanActive:(BOOL)scanActive
{
    if (_scanActive != scanActive) {
        _scanActive = scanActive;

        if (_scanActive) {
            _jyScanRectView.loading = NO;
            [self.jyQRTool jy_startScaning];
        }
        else{
            _jyScanRectView.loading = YES;
            [self.jyQRTool jy_stopScaning];
        }
    }
}

#pragma mark - Delegate
-(void)jy_willGetOutputMataDataObject
{
    self.scanActive = NO;
}

在viewWillAppear中,将品质设为YES,恢复生机扫码效率:

-(void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    self.scanActive = YES;
}

本来如此做未来,第一遍跻身扫码页面时loading图就不出新了,大家能够在viewDidLoad元帅实例变量初叶化为YES来解决,注意这里并非采取self.scanActive,直接用实例变量赋值不会调用setter方法:

- (void)viewDidLoad {
    [super viewDidLoad];

    self.view.backgroundColor = [UIColor blackColor];
    //self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"相册" style:UIBarButtonItemStylePlain target:self action:@selector(openPhotoLibrary)];
    self.navigationItem.title = @"扫一扫";

    _scanActive = YES;

   // ...
}

到了此处,扫码功效算是基本实现了,即使细节部分讲的可比啰嗦,但要么以为有不可缺乏写出来的。大家运转一向下探底视最后的功力:

图片 5

扫一扫

环视相册中的二维码

扫描相册中的二维码,首先展开相册然后先在UIImagePickerControllerDelegate代理方法中:

 #pragma mark - imagePickerController delegate
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
    //1.获取选择的图片
    UIImage *image = info[UIImagePickerControllerOriginalImage];
    //2.初始化一个监测器
    CIDetector*detector = [CIDetector detectorOfType:CIDetectorTypeQRCode context:nil options:@{ CIDetectorAccuracy : CIDetectorAccuracyHigh }];

    [picker dismissViewControllerAnimated:YES completion:^{
        //监测到的结果数组
        NSArray *features = [detector featuresInImage:[CIImage imageWithCGImage:image.CGImage]];
        if (features.count >=1) {
            /**结果对象 */
            CIQRCodeFeature *feature = [features objectAtIndex:0];
            NSString *scannedResult = feature.messageString;
            self.scannedResult=scannedResult;
            [self performSegueWithIdentifier:@"ToResult" sender:self];
        }
        else{
            [self showAlertTipWithTitle:@"提示" andMessage:@"该图片没有包含一个二维码!"];
        }
    }];
}

好了,关于扫描二维码前些天就提起那,下后生可畏篇作者会分享创立二维码作用。扫描二维码和创设二维码项目浮现:应用软件Store找出扫码神奇(记得给个好评呦卡塔尔。

越来越多精粹小说请关心Wechat民众账号:lecoding,你也得以扫描下方二维码关注大家。

qrcode_for_gh_af22362bf4bb_258.jpg

广播扫描音响效果

大家可以利用奥迪(Audi卡塔尔oToolbox这几个框架来达成广播自定义音响效果,基本原理是接纳二个soundID将自定义短音频注册到系统声音服务,然后利用那个ID来播放对应的短音频。关于奥迪(Audi卡塔尔(قطر‎oToolbox的详实介绍能够移动 iOS音响效果播放(奥迪oToolbox)

切切实实达成方式如下,在JYQRCodeTool中:

.h

//扫描音效文件名,其值为nil时不播放扫描音,默认为nil
@property(nonatomic,copy)NSString *scanVoiceName;

.m

#import <AudioToolbox/AudioToolbox.h>

//播放扫描音效
- (void)playScanSoundsWithName:(NSString *)soundName
{
    if (soundName) {
        // 获取音频文件路径
        NSURL *url = [[NSBundle mainBundle] URLForResource:soundName withExtension:nil];

        // 加载音效文件并创建 SoundID
        SystemSoundID soundID = 0;
        AudioServicesCreateSystemSoundID((__bridge CFURLRef)url, &soundID);

        // 设置播放完成回调
        //    AudioServicesAddSystemSoundCompletion(soundID, NULL, NULL, soundCompleteCallback, NULL);

        // 播放音效
        // 带有震动
        //    AudioServicesPlayAlertSound(_soundID);
        // 无振动
        AudioServicesPlaySystemSoundWithCompletion(soundID, ^{
            AudioServicesDisposeSystemSoundID(soundID);
        });

        // 销毁 SoundID
        //    AudioServicesDisposeSystemSoundID(soundID);
    }
}

内需在乎的是,这里的ID最佳利用1000~二〇〇二节制外的数字,这些节制内是系统短音效的ID,如今自家只晓得1007是系统的三全音,别的的ID代表怎么着大家能够慈悲去尝试看。

亟待增添扫描音响效果时,将音频文件拖入工程,然后在LazyLoad中设置scanVoiceName属性为文件名就可以

//LazyLoad
-(JYQRCodeTool *)jyQRTool
{
    if (!_jyQRTool) {
        _jyQRTool = [JYQRCodeTool toolsWithBindingController:self];
        _jyQRTool.scanVoiceName = @"sound.wav";
        _jyQRTool.delegate = self;
    }

    return _jyQRTool;
}
加上闪光灯按钮
/**
 * 控制闪光灯开关
 *
 * @param lock 闪光灯开关,YES时开启,NO时关闭,默认为NO
 **/
- (void)jy_controlTheFlashLight:(BOOL)lock;

- (void)jy_controlTheFlashLight:(BOOL)lock
{
    if (lock) {
        AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];

        NSError *error =nil;

        if([device hasTorch]) {
            BOOL locked = [device lockForConfiguration:&error];

            if(locked) {
                device.torchMode= AVCaptureTorchModeOn;
                [device unlockForConfiguration];
            }
        }
    }
    else{
        AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];

        if([device hasTorch]) {
            [device lockForConfiguration:nil];
            [device setTorchMode:AVCaptureTorchModeOff];
            [device unlockForConfiguration];
        }
    }
}
二维码生成/自定义颜色(iOS7.0上述可用)

二维码生成这部分参考了外人的篇章,基本上完全沿用了作者的代码及思路,依据本人的需求稍作修正,原来的文章地址 https://blog.yourtion.com/custom-cifilter-qrcode-generator.html

iOS7后头,CoreImage框架中有七个CIFilter类能够扶植大家将数据转载为二维码,实现进度很简短。由于二维码生成那有的没有必要用到调控器也无需工具类中的任何性质,大家能够写成类措施封装进JYQRCodeTool中:

/**
 * 将字符串转成二维码
 *
 * @param string  字符串
 * @param size    二维码图片尺寸
 *
 * @return 二维码图片
 **/
+ (UIImage *)jy_createQRCodeWithString:(NSString *)string size:(CGFloat)size;

+ (UIImage *)jy_createQRCodeWithString:(NSString *)string size:(CGFloat)size
{
    return [self createNonInterpolatedUIImageFormCIImage:[self createQRForString:string] withSize:size];
}

//生成二维码
+ (CIImage *)createQRForString:(NSString *)qrString {
    NSData *stringData = [qrString dataUsingEncoding:NSUTF8StringEncoding];
    // 创建filter
    CIFilter *qrFilter = [CIFilter filterWithName:@"CIQRCodeGenerator"];
    // 设置内容和纠错级别
    [qrFilter setValue:stringData forKey:@"inputMessage"];
    [qrFilter setValue:@"M" forKey:@"inputCorrectionLevel"];
    // 返回CIImage
    return qrFilter.outputImage;
}

//缩放二维码尺寸
+ (UIImage *)createNonInterpolatedUIImageFormCIImage:(CIImage *)image withSize:(CGFloat) size {
    CGRect extent = CGRectIntegral(image.extent);
    CGFloat scale = MIN(size/CGRectGetWidth(extent), size/CGRectGetHeight(extent));
    // 创建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);
    // 保存bitmap到图片
    CGImageRef scaledImage = CGBitmapContextCreateImage(bitmapRef);
    CGContextRelease(bitmapRef);
    CGImageRelease(bitmapImage);
    return [UIImage imageWithCGImage:scaledImage];
}

可以见见变化的一些有两步,第一步将NSString转成data,然后转成CIImage,由于CIImage间接转成UIImage后会现身失真的意况,画面会变模糊,所以还索要凭借imageView的尺寸举行缩放,缩放部分行使了Quartz2D相关知识点,这里暂不去探寻。

假使想纠正二维码的颜色,能够用下边包车型客车形式,以1个像素为单位,遍历图片中存有的像素点,将浅灰褐改为设置的颜料,将樱草黄改为透明,改成透亮的益处是,我们得认为二维码增添自定义背景及边框,相似Wechat中的二维码样式:

/**
 * 自定义二维码颜色
 *
 * @param qrImage  二维码图片
 * @param red      RGB通道-R
 * @param green    RGB通道-G
 * @param blue     RGB通道-B
 *
 * @return 二维码图片
 **/
+ (UIImage *)jy_customQRCodeWithImage:(UIImage *)qrImage colorWithRed:(CGFloat)red andGreen:(CGFloat)green andBlue:(CGFloat)blue;

+ (UIImage *)jy_customQRCodeWithImage:(UIImage *)qrImage colorWithRed:(CGFloat)red andGreen:(CGFloat)green andBlue:(CGFloat)blue
{
    return [self imageBlackToTransparent:qrImage withRed:red andGreen:green andBlue:blue];
}

//颜色填充
void ProviderReleaseData (void *info, const void *data, size_t size){
    free((void*)data);
}
+ (UIImage*)imageBlackToTransparent:(UIImage*)image withRed:(CGFloat)red andGreen:(CGFloat)green andBlue:(CGFloat)blue{
    const int imageWidth = image.size.width;
    const int imageHeight = image.size.height;
    size_t      bytesPerRow = imageWidth * 4;
    uint32_t *rgbImageBuf = (uint32_t *)malloc(bytesPerRow *imageHeight);
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGContextRef context = CGBitmapContextCreate(rgbImageBuf, imageWidth, imageHeight, 8, bytesPerRow, colorSpace,kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipLast);
    CGContextDrawImage(context, CGRectMake(0, 0, imageWidth, imageHeight), image.CGImage);
    // 遍历像素
    int pixelNum = imageWidth * imageHeight;
    uint32_t *pCurPtr = rgbImageBuf;
    for (int i = 0; i < pixelNum; i++, pCurPtr++){
        if ((*pCurPtr & 0xFFFFFF00) < 0x99999900)    // 将白色变成透明
        {
            // 改成下面的代码,会将图片转成想要的颜色
            uint8_t* ptr = (uint8_t*)pCurPtr;
            ptr[3] = red; //0~255
            ptr[2] = green;
            ptr[1] = blue;
        }
        else
        {
            uint8_t* ptr = (uint8_t*)pCurPtr;
            ptr[0] = 0;
        }
    }
    // 输出图片
    CGDataProviderRef dataProvider = CGDataProviderCreateWithData(NULL, rgbImageBuf, bytesPerRow * imageHeight, ProviderReleaseData);
    CGImageRef imageRef = CGImageCreate(imageWidth, imageHeight, 8, 32, bytesPerRow, colorSpace,
                                        kCGImageAlphaLast | kCGBitmapByteOrder32Little, dataProvider,
                                        NULL, true, kCGRenderingIntentDefault);
    CGDataProviderRelease(dataProvider);
    UIImage* resultUIImage = [UIImage imageWithCGImage:imageRef];
    // 清理空间
    CGImageRelease(imageRef);
    CGContextRelease(context);
    CGColorSpaceRelease(colorSpace);

    return resultUIImage;
}
为转移的二维码增加Logo

增加Logo那有的照样要动用绘图的相关文化,大约的笔触是,先对Logo图片作圆角边框管理,然后使用绘图将扭转的二维码与Logo画到一张画布上,最终输出画好的图片。必要小心的是,Logo需求放在大旨岗位,大小不要超过二维码尺寸的半数,只要在这里个范围内,基本上不会耳熏目染二维码的辨认。具体代码如下:

/**
 * 给二维码添加Logo
 *
 * @param qrImage      二维码图片
 * @param avatarImage  Logo图片
 * @param ratio        Logo圆角比例(0~1)0为无圆角,1为圆形
 *
 * @return 二维码图片
 **/
+ (UIImage *)jy_customQRCodeWithImage:(UIImage *)qrImage addAvatarImage:(UIImage *)avatarImage cornerRatio:(CGFloat)ratio;

+ (UIImage *)jy_customQRCodeWithImage:(UIImage *)qrImage addAvatarImage:(UIImage *)avatarImage cornerRatio:(CGFloat)ratio
{
    return [self imagewithQRImage:qrImage addAvatarImage:avatarImage ofTheSize:qrImage.size cornerRatio:ratio];
}

//添加logo
+ (UIImage *)imagewithQRImage:(UIImage *)qrImage addAvatarImage:(UIImage *)avatarImage ofTheSize:(CGSize)size cornerRatio:(CGFloat)ratio
{
    if (!avatarImage) {
        return qrImage;
    }
    BOOL opaque = 0.0;
    // 获取当前设备的scale
    CGFloat scale = [UIScreen mainScreen].scale;
    // 创建画布Rect
    CGRect qrRect = CGRectMake(0, 0, size.width, size.height);
    // 头像大小 _不能大于_ 画布的1/4 (这个大小之内的不会遮挡二维码的有效信息)
    CGFloat avatarWidth = (size.width/5.0);
    CGFloat avatarHeight = avatarWidth;
    //调用一个新的切割绘图方法 crop image add cornerRadius  (裁切头像图片为圆角,并添加bored   返回一个newimage)
    avatarImage = [self clipCornerRadius:avatarImage withSize:CGSizeMake(avatarWidth, avatarHeight) cornerRatio:ratio];
    // 设置头像的位置信息
    CGPoint position = CGPointMake(size.width/2.0, size.height/2.0);
    CGRect avatarRect = CGRectMake(position.x-(avatarWidth/2.0), position.y-(avatarHeight/2.0), avatarWidth, avatarHeight);
    // 设置画布信息
    UIGraphicsBeginImageContextWithOptions(size, opaque, scale);
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSaveGState(context);{// 开启画布
        // 翻转context (画布)
        CGContextTranslateCTM(context, 0, size.height);
        CGContextScaleCTM(context, 1, -1);
        // 根据 bgRect 用二维码填充视图
        CGContextDrawImage(context, qrRect, qrImage.CGImage);
        //  根据newAvatarImage 填充头像区域
        CGContextDrawImage(context, avatarRect, avatarImage.CGImage);
    }CGContextRestoreGState(context);// 提交画布
    // 从画布中提取图片
    UIImage *resultImage = UIGraphicsGetImageFromCurrentImageContext();
    // 释放画布
    UIGraphicsEndImageContext();

    return resultImage;
}

//logo圆角设置
+ (UIImage *)clipCornerRadius:(UIImage *)image withSize:(CGSize)size cornerRatio:(CGFloat)ratio
{
    // 白色border的宽度
    CGFloat outerWidth = size.width/15.0;
    // 黑色border的宽度
    CGFloat innerWidth = outerWidth/10.0;
    // 根据传入的ratio,设置圆角
    CGFloat corenerRadius = size.width/2.0 * ratio;
    // 为context创建一个区域
    CGRect areaRect = CGRectMake(0, 0, size.width, size.height);
    UIBezierPath *areaPath = [UIBezierPath bezierPathWithRoundedRect:areaRect byRoundingCorners:UIRectCornerAllCorners cornerRadii:CGSizeMake(corenerRadius, corenerRadius)];

    // 因为UIBezierpath划线是双向扩展的 初始位置就不会是(0,0)
    // path的位置就应该是你画的宽度的中间, 这个需要自己手动计算一下。
    // origin position
    CGFloat outerOrigin = outerWidth/2.0;
    CGFloat innerOrigin = innerWidth/2.0 + outerOrigin/1.2;
    CGRect outerRect = CGRectInset(areaRect, outerOrigin, outerOrigin);
    CGRect innerRect = CGRectInset(outerRect, innerOrigin, innerOrigin);
    // 要进行rect之间的计算,我想 "CGRectInset" 是一个不错的选择。
    //  外层path
    UIBezierPath *outerPath = [UIBezierPath bezierPathWithRoundedRect:outerRect byRoundingCorners:UIRectCornerAllCorners cornerRadii:CGSizeMake(outerRect.size.width/2.0 * ratio, outerRect.size.width/2.0 * ratio)];
    //  内层path
    UIBezierPath *innerPath = [UIBezierPath bezierPathWithRoundedRect:innerRect byRoundingCorners:UIRectCornerAllCorners cornerRadii:CGSizeMake(innerRect.size.width/2.0 * ratio, innerRect.size.width/2.0 * ratio)];
    // 要保证"内外层"的吻合,那就要进行比例相等,就能达到形状的完全匹配
    // 创建上下文
    UIGraphicsBeginImageContextWithOptions(size, NO, [UIScreen mainScreen].scale);
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSaveGState(context);{
        // 翻转context
        CGContextTranslateCTM(context, 0, size.height);
        CGContextScaleCTM(context, 1, -1);
        // context  添加 区域path -> 进行裁切画布
        CGContextAddPath(context, areaPath.CGPath);
        CGContextClip(context);
        // context 添加 背景颜色,避免透明背景会展示后面的二维码不美观的。(当然也可以对想遮住的区域进行clear操作,但是我当时写的时候还没有想到)
        CGContextAddPath(context, areaPath.CGPath);
        UIColor *fillColor = [UIColor colorWithRed:0.85 green:0.85 blue:0.85 alpha:1];
        CGContextSetFillColorWithColor(context, fillColor.CGColor);
        CGContextFillPath(context);
        // context 执行画头像
        CGContextDrawImage(context, innerRect, image.CGImage);
        // context 添加白色的边框 -> 执行填充白色画笔
        CGContextAddPath(context, outerPath.CGPath);
        CGContextSetStrokeColorWithColor(context, [UIColor whiteColor].CGColor);
        CGContextSetLineWidth(context, outerWidth);
        CGContextStrokePath(context);
        // context 添加黑色的边界 -> 执行填充黑色画笔
        CGContextAddPath(context, innerPath.CGPath);
        CGContextSetStrokeColorWithColor(context, [UIColor blackColor].CGColor);
        CGContextSetLineWidth(context, innerWidth);
        CGContextStrokePath(context);
    }CGContextRestoreGState(context);
    UIImage *radiusImage  = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    return radiusImage;
}

然后创立三个生成二维码的页面,具体代码就不贴出来了
生成二维码时,那样调用:

//生成二维码
-(void)generateQRCode
{
    [self controlBtnsEnabled:NO];
    [self resignTextFields];

    //根据view尺寸将字符串转为二维码
    UIImage *qrImage = [JYQRCodeTool jy_createQRCodeWithString:_textField.text size:_qrCodeView.bounds.size.width];

    NSArray <NSNumber *> *colorArr = @[[NSNumber numberWithFloat:_redField.text.floatValue],
                                       [NSNumber numberWithFloat:_greenField.text.floatValue],
                                       [NSNumber numberWithFloat:_blueField.text.floatValue]];



    //获取到RGB参数,根据颜色改变view背景色,否则会因为色差过小的问题导致无法识别
    BOOL isDarkBG = (colorArr[1].floatValue > 200);
    _qrCodeView.backgroundColor = isDarkBG?[UIColor blackColor]:[UIColor whiteColor];

    //为生成的二维码添加颜色
    qrImage = [JYQRCodeTool jy_customQRCodeWithImage:qrImage colorWithRed:colorArr[0].floatValue andGreen:colorArr[1].floatValue andBlue:colorArr[2].floatValue];


    if (_logoSwitch.isOn) {
        //为生成的二维码添加Logo
        qrImage = [JYQRCodeTool jy_customQRCodeWithImage:qrImage addAvatarImage:[UIImage imageNamed:@"logo"] cornerRatio:_logoSlider.value];
    }

    _qrCodeView.image = qrImage;

}

效果图:

图片 6

生成二维码

至于二维码阴影

意气风发种极其轻便的设置二维码阴影的点子正是给二维码所在的UIImageView加阴影:

_qrCodeView.layer.shadowColor = [UIColor grayColor].CGColor;
_qrCodeView.layer.shadowRadius = 1.5;
_qrCodeView.layer.shadowOpacity = 1.0;
_qrCodeView.layer.shadowOffset = CGSizeMake(0, 0);

急需小心的是,imageView的背景象要设置成透明才有机能,因为CALayer是遵照剧情来绘制阴影的,包涵边框和背景象,若是背景象不为透明,绘制出的黑影唯有边框外风流洒脱圈。

图片 7

二维码阴影

急需保留带有阴影的二维码图片时,要求将view范围内的有个别截图实行封存,因为影子不归于图片上的后生可畏都部队分,直接保存view上的图纸是从未影子效果的。

-(void)savePhoto
{
    UIImage *image = [self clipImageFromView:_qrBGView];
    UIImageWriteToSavedPhotosAlbum(image, self, @selector(image:didFinishSavingWithError:contextInfo:), nil);
}

-(UIImage *)clipImageFromView:(UIView *)view
{
    UIGraphicsBeginImageContextWithOptions(view.bounds.size, NO, 0.0);
    CGContextRef context = UIGraphicsGetCurrentContext();
    [view.layer renderInContext:context];
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    return image;
}
二维码识别(iOS8.0之上可用)

二维码识别部分比较轻易,直接提交代码:

/**
 * 识别图片中的二维码
 *
 * @param sourceImage  需要识别的图片
 *
 * @return 识别获得的数据
 **/
+ (NSString *)jy_detectorQRCodeWithSourceImage:(UIImage *)sourceImage;

+(NSString *)jy_detectorQRCodeWithSourceImage:(UIImage *)sourceImage
{
    return [self detectorQRCodeImageWithSourceImage:sourceImage];
}

+ (NSString *)detectorQRCodeImageWithSourceImage:(UIImage *)sourceImage
{
    // 0.创建上下文
    CIContext *context = [[CIContext alloc] init];
    // 1.创建一个探测器
    CIDetector *detector = [CIDetector detectorOfType:CIDetectorTypeQRCode context:context options:@{CIDetectorAccuracy: CIDetectorAccuracyLow}];

    // 2.直接开始识别图片,获取图片特征
    CIImage *imageCI = [[CIImage alloc] initWithImage:sourceImage];
    NSArray <CIFeature *> *features = [detector featuresInImage:imageCI];

    // 3.读取特征
    CIFeature *feature = features.firstObject;
    NSString *msgString = nil;

    if ([feature isKindOfClass:[CIQRCodeFeature class]]) {
        CIQRCodeFeature *tempFeature = (CIQRCodeFeature *)feature;
        msgString = tempFeature.messageString;
    }

    // 4.传递数据给外界
    return msgString;
}

需求识别app中的图片时,能够那样调用:

-(void)readQRCode
{
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        UIImage *qrImage = [self clipImageFromView:_qrBGView];

        NSString *urlStr = [JYQRCodeTool jy_detectorQRCodeWithSourceImage:qrImage];
        NSLog(@"%@",urlStr);

        if (!urlStr) {
            UIImage *scaleImage = [JYQRCodeTool jy_getImage:qrImage scaleToSize:CGSizeMake(200, 200)];
            urlStr = [JYQRCodeTool jy_detectorQRCodeWithSourceImage:scaleImage];
        }

        dispatch_async(dispatch_get_main_queue(), ^{
            //对识别出的数据进行处理
            if (urlStr) {
                WebViewController *webVC = [[WebViewController alloc] init];
                webVC.urlStr = urlStr;
                [self.navigationController pushViewController:webVC animated:YES];
            }
            else{
                UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"结果" message:@"未识别到有效信息" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
                [alert show];
            }
        });
    });
}

识别相册中的图片同理,然则必要专心的是,图片中二维码的高低恐怕会影响识别,测量试验中发觉有点图形不能够甄别,首要都以因为二维码尺寸太大,将尺寸改小之后又有什么不可辨认了,而略带图片原来能够辨别,改小后又不可能识别了。于是这里做了2次识别管理,先识别原图,假若不大概识别,将图纸改小后再识别一回,假如照旧不曾,间接再次回到nil。

识别相册中的二维码:

-(void)openPhotoLibrary
{
    if (![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary])
    {
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"照片权限已关闭" message:@"请到设置->隐私->照片中,允许app访问相册" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
        [alert show];
    }
    else{
        //开启相册前禁用扫码,防止相机扫到二维码后异常跳转
        [self.jyQRTool jy_stopScaning];

        UIImagePickerController *pickerCtr = [[UIImagePickerController alloc] init];
        pickerCtr.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
        pickerCtr.delegate = self;
        [self presentViewController:pickerCtr animated:YES completion:nil];
    }
}

-(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info
{
    self.callBackFromPhoto = YES;
    self.scanActive = NO;

    [picker dismissViewControllerAnimated:YES completion:^{

        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            UIImage *pickImage = [info objectForKey:UIImagePickerControllerOriginalImage];

            NSString *urlStr = [JYQRCodeTool jy_detectorQRCodeWithSourceImage:pickImage];

            if (!urlStr) {
                UIImage *scaleImage = [JYQRCodeTool jy_getImage:pickImage scaleToSize:CGSizeMake(200, 200)];
                urlStr = [JYQRCodeTool jy_detectorQRCodeWithSourceImage:scaleImage];
            }

            dispatch_async(dispatch_get_main_queue(), ^{
                //对获得的数据进行处理
                if (urlStr) {
                    [self visitWebViewWithUrl:urlStr];

                }
                else{
                    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"结果" message:@"未识别到有效信息" delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil];
                    [alert show];
                }
            });
        });
    }];
}

-(void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
{
    [picker dismissViewControllerAnimated:YES completion:^{
        //启用扫码
        [self.jyQRTool jy_startScaning];
    }]; 
}

-(void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex
{
    self.scanActive = YES;
}

鉴于相册入口放在扫码分界面中,最终大家须要针对相册弹出及再次来到时UI的刷新逻辑做一下优化:

@property(nonatomic,getter=isCallBackFromPhoto)BOOL callBackFromPhoto;

-(void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];

    //如果不是从相册选择了图片返回,启用扫码功能,否则显示loading
    if (!self.isCallBackFromPhoto) {
        self.scanActive = YES;
    }
    else{
        self.callBackFromPhoto = NO;
    }
}

在功成名就博得图片的delegate中(-imagePickerController: didFinishPickingMediaWithInfo:),设置self.callBackFromPhoto = YES。

末段效果图:

图片 8

生成二维码

图片 9

扫一扫

假若不思谋对iOS8以下做适配,那么以上内容应当丰裕满足app中二维码扫描的须求了。

demo地址 https://github.com/JiYuwei/QRCodeDemo

彩蛋:写完本文后闲暇翻阅了别人的篇章,找到那样风流洒脱篇 您的二维码那么酷,我必然是用了假二维码
里头有关二维码美化的某个看的自个儿头晕目眩 & 头皮发麻。。。
鲜明那几个时期,原始的青红皂白二维码已经敬敏不谢满足大家的审美需要了。

关于这个二维码的鼓吹原理,作者以后是毫无头绪。大概在明日,假诺临时光和精力的话,会去尝试破解那些谜题。各位看官借使有啥主张,款待在尘间留言探究。

编辑:编程 本文来源:二维码是app中非常常见的一个功能,下图显示付

关键词: