iOS 自定义短信验证码输入框

众多App经常使用的短信验证码输入框,没有啥好介绍的。

效果展示

基本原理

多个UITextField组成的,输入完成后,进入下一个UITextField进行编辑操作,最后一个输入完成后进行校验。

思维导图.png

自定义SMSCodeTextField

创建FLSMSCodeTextField 继承自UITextField,重写UIKeyInput- (void)deleteBackward;方法,增加自定义的UITextFieldDelegate

@protocol FLSMSCodeTextFieldDelegate <UITextFieldDelegate>
@optional;
/// 删除内容后的代理方法
/// @param textField 输入框
- (void)smsCodeTextFieldDeleteBackward:(FLSMSCodeTextField *)textField;

@end

@interface FLSMSCodeTextField : UITextField
/**
 删除的代理
 */
@property (nonatomic, assign) id<FLSMSCodeTextFieldDelegate> delegate;

@end
@implementation FLSMSCodeTextField

@dynamic delegate;

- (void)deleteBackward {
    [super deleteBackward];
    //删除操作的代理方法
    if (self.delegate && [self.delegate respondsToSelector:@selector(smsCodeTextFieldDeleteBackward:)]) {
        [self.delegate smsCodeTextFieldDeleteBackward:self];
    }
}

@end

FLSMSCodeEditView逻辑和代码

初始化

- (instancetype)init {
    if (self = [super init]) {
        [self p_initializeSubviews];
    }
    return self;
}

- (instancetype)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
        [self p_initializeSubviews];
    }
    return self;
}

- (void)p_initializeSubviews {
    self.textFieldArray = [NSMutableArray array];
    self.textFieldSeparatorLineArray = [NSMutableArray array];
    
    [self addSubview:self.contentView];
    [_contentView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.edges.mas_equalTo(UIEdgeInsetsZero);
    }];
}

设置需要创建的输入框数量和完成后的回调

/**
 验证码输入框数量
 */
@property (nonatomic, assign) NSInteger codeEditCount;

/**
 验证码输入完成的回调
 */
@property (nonatomic, copy) void (^smsCodeEditFinishBlock)(NSString *smsCode);
#pragma mark - setter

- (void)setCodeEditCount:(NSInteger)codeEditCount {
    _codeEditCount = codeEditCount;
    NSAssert(codeEditCount >= 4, @"数量不能少于4个");
    for (NSInteger i = 0; i < codeEditCount; i ++) {
        FLSMSCodeTextField *textField = [[FLSMSCodeTextField alloc]init];
        textField.tag = i;
        textField.keyboardType = UIKeyboardTypeNumberPad;
        textField.font = UIFontBoldMake(40);
        textField.textColor = UIColorBlack;
        textField.tintColor = UIColorGreen;
        textField.textAlignment = NSTextAlignmentCenter;
        textField.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
        textField.enabled = NO;
        textField.delegate = self;
        [textField addTarget:self action:@selector(p_textFieldEditingChangedAction:) forControlEvents:UIControlEventEditingChanged];
        
        [_contentView addSubview:textField];

        
        UIView *separatorLineView = [[UIView alloc]init];
        separatorLineView.backgroundColor = UIColorSeparator;
        [_contentView addSubview:separatorLineView];
        [separatorLineView mas_makeConstraints:^(MASConstraintMaker *make) {
            make.left.right.mas_equalTo(textField);
            make.top.mas_equalTo(textField.mas_bottom).mas_offset(14.0);
            make.height.mas_equalTo(PixelOne);
            make.bottom.mas_equalTo(0.0);
        }];

        [_textFieldArray addObject:textField];
        [_textFieldSeparatorLineArray addObject:separatorLineView];
    }
    [_textFieldArray mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.mas_equalTo(0.0);
        make.height.mas_equalTo(40.0);
    }];
    [_textFieldArray mas_distributeViewsAlongAxis:MASAxisTypeHorizontal withFixedSpacing:14.0 leadSpacing:0.0 tailSpacing:0.0];
}

输入内容时的逻辑判断方法

#pragma mark - Action

- (void)p_textFieldEditingChangedAction:(FLSMSCodeTextField *)textField {
    //判断是否为最后一个textField
    if (textField.tag == self.textFieldArray.count - 1 && textField.text.length > 0) {
        //判断text的长度
        textField.text = textField.text.length > 1 ? [textField.text substringFromIndex:1] : [textField.text substringWithRange:NSMakeRange(0, 1)];
        [textField resignFirstResponder];
        textField.enabled = NO;
        //判断SMSCode已经达到最大数量,且又增加更改
        if (self.smsCodeString.length == self.codeEditCount) {
            self.smsCodeString = [self.smsCodeString substringToIndex:self.smsCodeString.length - 1];
        }
        self.smsCodeString = [self.smsCodeString stringByAppendingString:textField.text];
        
        if (self.smsCodeEditFinishBlock) {
            self.smsCodeEditFinishBlock(self.smsCodeString);
        }
        QMUILog(NSStringFromClass(self.class), @"%@",self.smsCodeString);
        
    } else {
        if (![textField.text isEqualToString:@""]) {
            FLSMSCodeTextField *nextTF = self.textFieldArray[textField.tag + 1];
            nextTF.enabled = YES;
            if (textField.text.length > 1) {
                NSString *lengthString = textField.text;
                textField.text = [lengthString substringWithRange:NSMakeRange(0, 1)];
                nextTF.text = [lengthString substringWithRange:NSMakeRange(lengthString.length - 1, 1)];
                //判断是否是最后一个
                if (nextTF.tag == self.textFieldArray.count - 1 && textField.text.length > 0) {
                    [nextTF resignFirstResponder];
                    nextTF.enabled = NO;
                    self.smsCodeString = self.smsCodeString = [self.smsCodeString stringByAppendingString:nextTF.text];
                    if (self.smsCodeEditFinishBlock) {
                        self.smsCodeEditFinishBlock(self.smsCodeString);
                    }
                    QMUILog(NSStringFromClass(self.class), @"%@",self.smsCodeString);
                } else {
                    [nextTF becomeFirstResponder];
                }
            } else {
                [nextTF becomeFirstResponder];
            }
            textField.enabled = NO;

        }
        self.smsCodeString = @"";
        for (FLSMSCodeTextField *tf in self.textFieldArray) {
            if (tf.text.length > 0) {
                self.smsCodeString = [self.smsCodeString stringByAppendingString:tf.text];
            }
        }
    }
    
}

删除内容时的逻辑判断方法

#pragma mark - FLSMSCodeTextFieldDelegate

- (void)smsCodeTextFieldDeleteBackward:(FLSMSCodeTextField *)textField {
    if (textField.tag != 0 && !textField.text.length) {
        textField.enabled = NO;
        FLSMSCodeTextField *beforeTextField = self.textFieldArray[textField.tag - 1];
        beforeTextField.enabled = YES;
        [beforeTextField becomeFirstResponder];
    }
}

- (void)p_closeAllTextFieldEnabled {
    for (FLSMSCodeTextField *textField in self.textFieldArray) {
        textField.enabled = NO;
    }
}

点击编辑视图时,需要触发第一响应的方法

- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [self p_closeAllTextFieldEnabled];
    if (self.smsCodeString.length) {
        if (self.smsCodeString.length == self.codeEditCount) {
            FLSMSCodeTextField *textField = self.textFieldArray.lastObject;
            textField.enabled = YES;
            [textField becomeFirstResponder];
        } else {
            FLSMSCodeTextField *textField = self.textFieldArray[self.smsCodeString.length];
            textField.enabled = YES;
            [textField becomeFirstResponder];
        }
        
    } else {
        FLSMSCodeTextField *textField = self.textFieldArray.firstObject;
        textField.enabled = YES;
        [textField becomeFirstResponder];
    }
    
}

最后

因为输入和删除都会触发p_textFieldEditingChangedAction方法,所以内部逻辑只针对输入内容时,删除内容会重新赋值smsCodeString,最后大家注意避免stringByAppendingString不能添加为空的字符串。

// 先置空smsCodeString,删除/输入时都会触发此方法
self.smsCodeString = @"";
for (VPSMSCodeTextField *tf in self.textFieldArray) {
     if (tf.text.length > 0) {
        self.smsCodeString = [self.smsCodeString stringByAppendingString:tf.text];
     }
}

以上就是一个简单的验证码的所有内容,以后追加输入框选中效果。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 221,576评论 6 515
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,515评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 168,017评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,626评论 1 296
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,625评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,255评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,825评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,729评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,271评论 1 320
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,363评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,498评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,183评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,867评论 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,338评论 0 24
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,458评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,906评论 3 376
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,507评论 2 359

推荐阅读更多精彩内容