iOS 验证码输入框

特性

  • 支持自定义验证码长度(默认长度为6)
  • 支持切换键盘类型,适应纯数字、数字+字母等组合(默认为系统数字键盘)
  • 验证码输入完毕后自动收起键盘并通过block回调调用者

效果图

源码

NS_AVAILABLE_IOS(9_0)
@interface VCVerifyCodeInputView : UIView

/// 验证码长度,默认`6`
@property (nonatomic, assign) NSInteger length;
/// 键盘类型,默认`UIKeyboardTypeNumberPad`
@property (nonatomic, assign) UIKeyboardType keyboardType;
/// 验证码输入完毕后的回调
@property (nonatomic, copy) void(^didInputCompletionHandler)(NSString *verifycode);
/// 文本框高亮颜色,默认(30,144,255)
@property (nonatomic, strong) UIColor *focusColor;

/// 获取用户输入的验证码
@property (nonatomic, copy, readonly) NSString *verifycode;

@end
@interface VCVerifyCodeInputView ()<UITextFieldDelegate>

@end

@implementation VCVerifyCodeInputView
{
    UIStackView *stackView;
    UITextField *textField;
}

#pragma mark - Lifecycle

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

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    if (self = [super initWithCoder:aDecoder]) {
        [self setup];
    }
    return self;
}

- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UITextFieldTextDidChangeNotification object:textField];
}

#pragma mark - <UITextFieldDelegate>

- (void)textFieldDidBeginEditing:(UITextField *)textField {
    [self updateInputIndicator];
}

- (void)textFieldDidEndEditing:(UITextField *)textField {
    for (UIView *view in stackView.arrangedSubviews) {
        view.layer.borderColor = [[UIColor blackColor] CGColor];
    }
    
    if (textField.text.length == self.length && self.didInputCompletionHandler) {
        self.didInputCompletionHandler(textField.text);
    }
}

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
    if (range.length == 0) { // Add.
        if (textField.text.length == self.length) {
            return NO;
        }
        NSString *regexp = @"^[A-Za-z0-9]{1}$";
        NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", regexp];
        return [predicate evaluateWithObject:string];
    }
    return YES;
}

#pragma mark - Actions

- (void)textFieldTextDidChangeNotificationAction:(NSNotification *)sender {
    NSString *text = textField.text;
    [self updateInputIndicator];
    if (textField.isEditing && text.length == self.length) {
        // 收起键盘并回调block
        [textField resignFirstResponder];
    }
}

#pragma mark - Private

- (void)setup {
    stackView = [[UIStackView alloc] init];
    stackView.axis = UILayoutConstraintAxisHorizontal;
    stackView.distribution = UIStackViewDistributionFillEqually;
    stackView.spacing = 10.0;
    [self addSubviewToFill:stackView];
    
    textField = [[UITextField alloc] init];
    textField.borderStyle = UITextBorderStyleNone;
    textField.backgroundColor = [UIColor clearColor];
    textField.keyboardType = UIKeyboardTypeNumberPad;
    textField.autocorrectionType = UITextAutocorrectionTypeNo; // 禁用自动联想功能
    textField.tintColor = [UIColor clearColor]; // 清除光标颜色
    textField.textColor = [UIColor clearColor]; // 清除文字颜色
    textField.delegate = self;
    [self addSubviewToFill:textField];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textFieldTextDidChangeNotificationAction:) name:UITextFieldTextDidChangeNotification object:textField];
    
    [self setLength:6];
    self.focusColor = [UIColor colorWithRed:30/255.0 green:144/255.0 blue:255/255.0 alpha:1.0];
}

/// 更新输入指示器状态
- (void)updateInputIndicator {
    NSString *text = textField.text;
    for (NSInteger idx=0; idx<self.length; idx++) {
        UILabel *label = (UILabel *)stackView.arrangedSubviews[idx];
        if (text.length && idx < text.length) {
            label.text = [NSString stringWithFormat:@"%C", [text characterAtIndex:idx]];
        } else {
            label.text = nil;
        }
        if (idx == text.length) {
            label.layer.borderColor = [self.focusColor CGColor];
        } else {
            label.layer.borderColor = [UIColor blackColor].CGColor;
        }
    }
}

- (void)addSubviewToFill:(UIView *)subview {
    [self addSubview:subview];
    subview.translatesAutoresizingMaskIntoConstraints = NO;
    [subview.topAnchor constraintEqualToAnchor:self.topAnchor].active = YES;
    [subview.bottomAnchor constraintEqualToAnchor:self.bottomAnchor].active = YES;
    [subview.leadingAnchor constraintEqualToAnchor:self.leadingAnchor].active = YES;
    [subview.trailingAnchor constraintEqualToAnchor:self.trailingAnchor].active = YES;
}

#pragma mark - setter & getter

- (void)setLength:(NSInteger)length {
    [stackView.arrangedSubviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
    for (NSInteger idx=0; idx<length; idx++) {
        UILabel *label = [[UILabel alloc] init];
        label.textAlignment = NSTextAlignmentCenter;
        label.backgroundColor = [UIColor clearColor];
        label.layer.borderWidth = 1.0;
        label.layer.borderColor = [[UIColor blackColor] CGColor];
        label.layer.cornerRadius = 5.0;
        label.layer.masksToBounds = YES;
        [stackView addArrangedSubview:label];
    }
}

- (NSInteger)length {
    return stackView.arrangedSubviews.count;
}

- (void)setKeyboardType:(UIKeyboardType)keyboardType {
    textField.keyboardType = keyboardType;
}

- (UIKeyboardType)keyboardType {
    return textField.keyboardType;
}

- (NSString *)verifycode {
    return textField.text;
}

@end

使用示例

VCVerifyCodeInputView *verifyCodeView = [[VCVerifyCodeInputView alloc] init];
verifyCodeView.frame = CGRectMake(20.0, 100.0, 340.0, 40.0);
verifyCodeView.length = 8;
verifyCodeView.focusColor = [UIColor redColor];
[verifyCodeView setDidInputCompletionHandler:^(NSString *verifycode) {
    NSLog(@"输入的验证码是: %@", verifycode);
}];
[self.view addSubview:verifyCodeView];