iOS学习——输入验证码界面封装
在很多app中都有输入验证码的功能需求,最近项目需要也有这个功能。做完之后简单整理了一下,将实现的基本思路做下记录。实现后的效果大致如下图所示,当四位签到码全部输入时,提交按钮是可以提交的,否则提交按钮失效,不允许提交。
1 整体布局
上图整个界面的布局很简单,就不多说了,重点就是中间这一块的验证码输入功能,我把它单独封装拿出来封装在一个自定义view(klcoderesignview)里了,下图是klcoderesignview布局的层次结构。
验证码输入视图(klcoderesignview)的最底层用一个透明的uitextfield来接收键盘的输入信息,上面则用4个展示视图(klcodeview)来分别展示输入的验证码信息,所有的展示视图(klcodeview)都放在一个数组中,方便后续的访问和调用。所以,klcoderesignview应该向外提供两个处理入口,验证码输入完成和输入未完成时的操作入口,并在完成时提供输入验证码信息,这里我们采用block的方式进行向外提供操作入口。此外,我们还提供了一个可以修改验证码位数的入口,调用 initwithcodebits: 即可设置验证码的位数。klcoderesignview.h以及klcoderesignview分类的代码如下:
#import <uikit/uikit.h> ns_assume_nonnull_begin typedef void (^coderesigncompleted)(nsstring *content); typedef void (^coderesignuncompleted)(nsstring *content); @interface klcoderesignview : uiview @property (copy, nonatomic) coderesigncompleted coderesigncompleted; @property (copy, nonatomic) coderesignuncompleted coderesignuncompleted; - (instancetype) initwithcodebits:(nsinteger)codebits; @end
@interface klcoderesignview () <uitextfielddelegate> @property (strong, nonatomic) uitextfield *contentf; //监听内容输入 @property (strong, nonatomic) nsarray<klcodeview *> *codeviewsarr;//展示验证码内容的codeview数组 @property (assign, nonatomic) nsinteger currindex;//当前待输入的codeview的下标 @property (assign, nonatomic) nsinteger codebits;//位数 @end
2 注意点
2.1 信息输入框uitextfield
信息输入框uitextfield是最重要的一部分,布局在klcoderesignview的最底层,主要作用是用于接收验证码的输入,但是对应的光标肯定是不能显示出来的,而且该uitextfield不能进行复制、粘贴、选择等操作。所以信息输入框contentf的配置如下:
- (uitextfield *)contentf { if (!_contentf) { _contentf = [[uitextfield alloc] init]; //背景颜色和字体颜色都设置为透明的,这样在界面上就看不到 _contentf.backgroundcolor = [uicolor clearcolor]; _contentf.textcolor = [uicolor clearcolor]; _contentf.keyboardtype = uikeyboardtypenumberpad;//数字键盘 _contentf.returnkeytype = uireturnkeydone;//完成 _contentf.tintcolor = [uicolor clearcolor];//设置光标的颜色 _contentf.delegate = self; } return _contentf; }
最后,我们通过添加uitextfield的分类来实现屏蔽复制、粘贴、选择等操作,其实这些都是在uitextfield的 - (bool)canperformaction:(sel)action withsender:(id)sender 进行控制的,返回yes则允许,否则不允许,所以这里我们不管什么操作,全部返回no,这就屏蔽了所有的操作。
@implementation uitextfield (forbiddenselect) /* 该函数控制是否允许 选择 全选 剪切 f粘贴等功能,可以针对不同功能进行限制 返回yes表示允许对应的功能,返回no则表示不允许对应的功能 直接返回no则表示不允许任何编辑 */ - (bool)canperformaction:(sel)action withsender:(id)sender { return no; } @end
2.2 展示视图(klcodeview)
展示视图(klcodeview)就很简单了,布局就是一个uilabel在上面,最下面一个uiview的下划线,唯一需要考虑的点就是下划线的颜色问题,如何根据是否有内容进行颜色变化。这个问题的解决也很简单,因为这个 uilabel的内容是通过一个属性text来进行设置的,所以我们重写text的设置方法就ok了,当设置的text内容不为空时,我们就设置对应的颜色为需要的颜色(蓝色),否则设置为灰色。
- (void)settext:(nsstring *)text { if (text.length > 0) {//有数据时设置为蓝色 self.codelabel.text = [text substringtoindex:1];//只取一位数 self.lineview.backgroundcolor = [uicolor bluecolor]; } else { self.codelabel.text = @""; self.lineview.backgroundcolor = [uicolor graycolor]; } }
2.3 输入逻辑处理
输入处理逻辑就是在输入和删除时进内容进行判断,并将对应的内容显示到对应的展示视图(klcodeview)中,内容的输入就都在uitextfield的代理uitextfielddelegate中的 - (bool)textfield: shouldchangecharactersinrange: replacementstring: 方法中。
- 我们用属性currindex来表示当前待输入的展示视图klcodeview的下标,所以,当输入一个合法的验证码时,currindex要加1,当删除一个验证码时,currindex要减1,并且当currindex == 0时,删除按钮不起作用,currindex不再减1了。
- 如果在验证码输入完成和未完成时做不同的处理,通过我们前面提供的两个block coderesigncompleted 和 coderesignuncompleted 就可以了,我们再这里通过判断currindex 是否等于 self.codebits,相等则完成,否则没有完成,并且调用对应的block进行处理。
- 对输入内容进行判断是否是纯数字,这个很简单,判断方法网上有很多方案,这里也简单地贴在下面的代码中。
- 对输入的字符串的长度进行判断,如果超过当前位数,则输入无效。
- 完成、删除操作的判断一定要在是否是纯数字以及位数过长判断之前,否则可能会导致完成、删除操作失效。
#pragma mark --- uitextfield delegate - (bool)textfield:(uitextfield *)textfield shouldchangecharactersinrange:(nsrange)range replacementstring:(nsstring *)string { //完成 则收回键盘 if ([string isequaltostring:@"\n"]) { [textfield resignfirstresponder]; return no; } //删除 操作 if ([string isequaltostring:@""]) { if (self.currindex == 0) {//待输入的下标为0时 删除时下标不变化,否则下标减1 self.codeviewsarr[self.currindex].text = string; } else { self.codeviewsarr[--self.currindex].text = string; if (self.coderesignuncompleted) { nsstring *content = [textfield.text substringtoindex:self.currindex]; self.coderesignuncompleted(content); } } return yes; } //判断 输入的是否是纯数字,不是纯数字 输入无效 if (![self judgepureint:string]) { return no; } //如果输入的内容超过了验证码的长度 则输入无效 if ((textfield.text.length + string.length) > self.codebits) { return no; } //输入的数字,则当前待输入的下标对应的 view中添加输入的数字,并且下标加1 self.codeviewsarr[self.currindex++].text = string; //当当前待输入的下标为codebits时表示已经输入了对应位数的验证码,执行完成操作 if (self.currindex == self.codebits && self.coderesigncompleted) { nsstring *content = [nsstring stringwithformat:@"%@%@", textfield.text, string]; self.coderesigncompleted(content); } else { if (self.coderesignuncompleted) { nsstring *content = [nsstring stringwithformat:@"%@%@", textfield.text, string]; self.coderesignuncompleted(content); } } return yes; }
//判断一个字符串是都是纯数字 - (bool)judgepureint:(nsstring *)content { nsscanner *scan = [nsscanner scannerwithstring:content]; int val; return [scan scanint:&val] && [scan isatend]; }
3 使用
使用时只需要创建对应的view进行布局就ok了,然后设置验证码输入完成和验证码输入未完成对应的处理方案。
- (void)viewdidload { [super viewdidload]; self.view.backgroundcolor = [uicolor whitecolor]; weakself klcoderesignview *codeview = [[klcoderesignview alloc] initwithcodebits:4]; codeview.coderesigncompleted = ^(nsstring * _nonnull content) { //对应位数输入完成时 允许提交按钮有效 允许提交 nslog(@"%@", content); weakself.submitbtn.enabled = yes; weakself.submitbtn.alpha = 1.0f; }; codeview.coderesignuncompleted = ^(nsstring * _nonnull content) { //对应位数未输入完成时 提交按钮失效 不允许提交 weakself.submitbtn.enabled = no; weakself.submitbtn.alpha = 0.5f; }; [self.view addsubview:codeview]; [codeview mas_makeconstraints:^(masconstraintmaker *make) { make.left.mas_equalto(weakself.view).mas_offset(15.0f); make.right.mas_equalto(weakself.view).mas_offset(-15.0f); make.top.mas_equalto(weakself.view).mas_offset(100.0f); make.height.mas_equalto(40.0f); }]; _submitbtn = [uibutton buttonwithtype:uibuttontypecustom]; _submitbtn.titlelabel.font = font(17.0f); [_submitbtn settitle:@"提交" forstate:uicontrolstatenormal]; [_submitbtn settitlecolor:[uicolor whitecolor] forstate:uicontrolstatenormal]; [_submitbtn setbackgroundcolor:xrgb(3d,9a,e8)]; _submitbtn.enabled = no; _submitbtn.alpha = 0.5f; _submitbtn.layer.cornerradius = 5.0f; // [submitbtn addtarget:self action:@selector(submitbtnclicked:) forcontrolevents:uicontroleventtouchupinside]; [self.view addsubview:_submitbtn]; [_submitbtn mas_makeconstraints:^(masconstraintmaker *make) { make.left.mas_equalto(weakself.view).mas_offset(20.0f); make.right.mas_equalto(weakself.view).mas_offset(-20.0f); make.top.mas_equalto(weakself.view).mas_offset(260.0f); make.height.mas_equalto(45.0f); }]; }
所有的代码如下,主要分为两个文件,一个 klcoderesignview.h,一个klcoderesignview.m,如下:
#import <uikit/uikit.h> ns_assume_nonnull_begin typedef void (^coderesigncompleted)(nsstring *content); typedef void (^coderesignuncompleted)(nsstring *content); @interface klcoderesignview : uiview @property (copy, nonatomic) coderesigncompleted coderesigncompleted; @property (copy, nonatomic) coderesignuncompleted coderesignuncompleted; - (instancetype) initwithcodebits:(nsinteger)codebits; @end
#import "klcoderesignview.h" #define weakself typeof(self) __weak weakself = self; //自定义 验证码展示视图 view,由一个label和一个下划线组成 @interface klcodeview : uiview @property (strong, nonatomic) nsstring *text; @property (strong, nonatomic) uilabel *codelabel; @property (strong, nonatomic) uiview *lineview; @end @interface klcoderesignview () <uitextfielddelegate> @property (strong, nonatomic) uitextfield *contentf; //监听内容输入 @property (strong, nonatomic) nsarray<klcodeview *> *codeviewsarr;//显示输入内容的codeview数组 @property (assign, nonatomic) nsinteger currindex;//当前待输入的codeview的下标 @property (assign, nonatomic) nsinteger codebits;//位数 @end @implementation klcoderesignview - (instancetype)initwithcodebits:(nsinteger)codebits { self = [super init]; self.backgroundcolor = [uicolor whitecolor]; self.codebits = codebits; if (self) { //验证码默认是4位 if (self.codebits < 1) { self.codebits = 4; } weakself [self addsubview:self.contentf]; [self.contentf mas_makeconstraints:^(masconstraintmaker *make) { make.top.bottom.right.left.mas_equalto(weakself).mas_offset(0.0f); }]; for(nsinteger i = 0; i < self.codebits; i++) { klcodeview *codeview = self.codeviewsarr[i]; [self addsubview:codeview]; } } return self; } - (void)layoutsubviews { [super layoutsubviews]; weakself //设定每个数字之间的间距为数字view宽度的一半 总宽度就是 bits + (bits - 1)* 0.5 cgfloat codeviewwidth = self.bounds.size.width/(self.codebits * 1.5 - 0.5); for(nsinteger i = 0; i < self.codebits; i++) { klcodeview *codeview = self.codeviewsarr[i]; [codeview mas_updateconstraints:^(masconstraintmaker *make) { cgfloat left = codeviewwidth * 1.5 * i; make.left.mas_equalto(weakself).mas_offset(left); make.top.bottom.mas_equalto(weakself).mas_offset(0.0f); make.width.mas_equalto(codeviewwidth); }]; } } #pragma mark --- uitextfield delegate - (bool)textfield:(uitextfield *)textfield shouldchangecharactersinrange:(nsrange)range replacementstring:(nsstring *)string { //完成 则收回键盘 if ([string isequaltostring:@"\n"]) { [textfield resignfirstresponder]; return no; } //删除 操作 if ([string isequaltostring:@""]) { if (self.currindex == 0) {//待输入的下标为0时 删除时下标不变化,否则下标减1 self.codeviewsarr[self.currindex].text = string; } else { self.codeviewsarr[--self.currindex].text = string; if (self.coderesignuncompleted) { nsstring *content = [textfield.text substringtoindex:self.currindex]; self.coderesignuncompleted(content); } } return yes; } //判断 输入的是否是纯数字,不是纯数字 输入无效 if (![self judgepureint:string]) { return no; } //如果输入的内容超过了验证码的长度 则输入无效 if ((textfield.text.length + string.length) > self.codebits) { return no; } //输入的数字,则当前待输入的下标对应的 view中添加输入的数字,并且下标加1 self.codeviewsarr[self.currindex++].text = string; //当当前待输入的下标为codebits时表示已经输入了对应位数的验证码,执行完成操作 if (self.currindex == self.codebits && self.coderesigncompleted) { nsstring *content = [nsstring stringwithformat:@"%@%@", textfield.text, string]; self.coderesigncompleted(content); } else { if (self.coderesignuncompleted) { nsstring *content = [nsstring stringwithformat:@"%@%@", textfield.text, string]; self.coderesignuncompleted(content); } } return yes; } - (uitextfield *)contentf { if (!_contentf) { _contentf = [[uitextfield alloc] init]; //背景颜色和字体颜色都设置为透明的,这样在界面上就看不到 _contentf.backgroundcolor = [uicolor clearcolor]; _contentf.textcolor = [uicolor clearcolor]; _contentf.keyboardtype = uikeyboardtypenumberpad;//数字键盘 _contentf.returnkeytype = uireturnkeydone;//完成 _contentf.tintcolor = [uicolor clearcolor];//设置光标的颜色 _contentf.delegate = self; } return _contentf; } - (nsarray<klcodeview *> *)codeviewsarr { if (!_codeviewsarr) { nsmutablearray *arr = [nsmutablearray array]; for (nsinteger i = 0; i < self.codebits; i++) { klcodeview *codeview = [[klcodeview alloc] init]; [arr addobject:codeview]; } _codeviewsarr = [nsarray arraywitharray:arr]; } return _codeviewsarr; } //判断一个字符串是都是纯数字 - (bool)judgepureint:(nsstring *)content { nsscanner *scan = [nsscanner scannerwithstring:content]; int val; return [scan scanint:&val] && [scan isatend]; } @end @implementation uitextfield (forbiddenselect) /* 该函数控制是否允许 选择 全选 剪切 f粘贴等功能,可以针对不同功能进行限制 返回yes表示允许对应的功能,返回no则表示不允许对应的功能 直接返回no则表示不允许任何编辑 */ - (bool)canperformaction:(sel)action withsender:(id)sender { return no; } @end //验证码展示视图 的实现 @implementation klcodeview - (instancetype)initwithframe:(cgrect)frame { self = [super initwithframe:frame]; if (self) { weakself self.backgroundcolor = [uicolor whitecolor]; self.userinteractionenabled = no; //数字编码 label _codelabel = [[uilabel alloc] init]; _codelabel.textcolor = [uicolor bluecolor]; _codelabel.font = font(25.0f); _codelabel.textalignment = nstextalignmentcenter; [self addsubview:_codelabel]; [_codelabel mas_makeconstraints:^(masconstraintmaker *make) { make.top.left.right.mas_equalto(weakself).mas_offset(0.0f); make.bottom.mas_equalto(weakself).mas_offset(-10.0f); }]; _lineview = [[uiview alloc] init]; _lineview.backgroundcolor = [uicolor graycolor]; [self addsubview:_lineview]; [_lineview mas_makeconstraints:^(masconstraintmaker *make) { make.bottom.left.right.mas_equalto(weakself).mas_offset(0.0f); make.height.mas_equalto(2.0f); }]; } return self; } - (void)settext:(nsstring *)text { if (text.length > 0) { self.codelabel.text = [text substringtoindex:1]; self.lineview.backgroundcolor = [uicolor bluecolor]; } else { self.codelabel.text = @""; self.lineview.backgroundcolor = [uicolor graycolor]; } } @end
上一篇: 爆囧,夫妻的笑事儿真是多