欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

iOS学习——输入验证码界面封装

程序员文章站 2022-06-14 23:41:24
在很多App中都有输入验证码的功能需求,最近项目需要也有这个功能。做完之后简单整理了一下,将实现的基本思路做下记录。实现后的效果大致如下图所示,当四位签到码全部输入时,提交按钮是可以提交的,否则提交按钮失效,不允许提交。 1 整体布局 上图整个界面的布局很简单,就不多说了,重点就是中间这一块的验证码 ......

  在很多app中都有输入验证码的功能需求,最近项目需要也有这个功能。做完之后简单整理了一下,将实现的基本思路做下记录。实现后的效果大致如下图所示,当四位签到码全部输入时,提交按钮是可以提交的,否则提交按钮失效,不允许提交。

            iOS学习——输入验证码界面封装        iOS学习——输入验证码界面封装

1 整体布局

   上图整个界面的布局很简单,就不多说了,重点就是中间这一块的验证码输入功能,我把它单独封装拿出来封装在一个自定义view(klcoderesignview)里了,下图是klcoderesignview布局的层次结构。

            iOS学习——输入验证码界面封装        iOS学习——输入验证码界面封装

  验证码输入视图(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,如下:

iOS学习——输入验证码界面封装
#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
klcoderesignview.h
iOS学习——输入验证码界面封装
#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
klcoderesignview.m