实现效果:
第一种:
使用多个UITextField来做,底部直接使用UIView画线,这也是最简单的做法,需要处理较多的第一响应事件,如果采用这种方式来做,需要注意的一个坑就是当当前的输入框没有值的时候点击退格键需要删除上一个Field的文字并光标跳转到上一个field,当输入框中没有文字的时候通过UITextField的代理是监听不到退格键点击事件的,需要重写UITextField 的 deleteBackward 方法来监听。
我第一次就是这么实现的,但是后来有个需求是当用户点击任意一个Field的时候光标不能响应在对应的Field上,而是响应在当前输入的最后一位上,只允许用户从最后一位开始删除。这又需要做一些额外的处理,代码量会越来越多,所以我采取了第二种方式。
第二种:
通过一个UITextField 和 一个 UILabel来实现。
UITextField并不显示,只是用来调用起输入键盘以及监听用户的输入。
UILabel通过重新绘制来绘制对应的UI和验证码。
这种方式是我查阅了很多网上的资料后发现的比较合理的一种方式,并进行了一些优化,重点就是绘制的光标添加闪动的动画效果以及控制光标的移动,为了实现这个效果,我放弃了使用上下文的方式来绘制,转而使用的贝塞尔曲线来绘制。
上代码:
YCVerificationCodeInputLabel.h
#import
@interface YCVerificationCodeInputLabel : UILabel
@property (nonatomic, assign) NSInteger numberOfVertificationCode;
@end
YCVerificationCodeInputLabel.m
#import "YCVerificationCodeInputLabel.h"
@interface YCVerificationCodeInputLabel ()
/** 上一次的光标 */
@property (nonatomic, strong) CAShapeLayer *lastShapeLayer;
@end
@implementation YCVerificationCodeInputLabel
//重写setText方法,当text改变时手动调用drawRect方法,将text的内容按指定的格式绘制到label上
- (void)setText:(NSString *)text {
[super setText:text];
// 手动调用drawRect方法
[self setNeedsDisplay];
}
// 按照指定的格式绘制验证码
- (void)drawRect:(CGRect)rect1 {
//计算每位验证码的所在区域的宽和高
CGRect rect = CGRectMake(0, 0, 208, 48);
float width = (rect.size.width - 16 * (self.numberOfVertificationCode - 1)) / (float)self.numberOfVertificationCode;
float height = rect.size.height;
// 将每位验证码绘制到指定区域
for (int i = 0; i < self.text.length; i++) {
// 计算每位验证码的绘制区域
CGRect tempRect = CGRectMake(i * (width + 16), 0, width, height);
// 遍历验证码的每个字符
NSString *charecterString = [NSString stringWithFormat:@"%c", [self.text characterAtIndex:i]];
// 设置验证码的现实属性
NSMutableDictionary *attributes = [[NSMutableDictionary alloc] init];
attributes[NSFontAttributeName] = self.font;
attributes[NSForegroundColorAttributeName] = self.textColor;
// 计算每位验证码的绘制起点(为了使验证码位于tempRect的中部,不应该从tempRect的重点开始绘制)
// 计算每位验证码的在指定样式下的size
CGSize characterSize = [charecterString sizeWithAttributes:attributes];
CGPoint vertificationCodeDrawStartPoint = CGPointMake(i * (width + 16) + (width - characterSize.width) / 2.0, (tempRect.size.height - characterSize.height) / 2.0);
// 绘制验证码
[charecterString drawAtPoint:vertificationCodeDrawStartPoint withAttributes:attributes];
}
//绘制底部横线
for (int k = 0; k < self.numberOfVertificationCode; k++) {
[self drawBottomLineWithRect:rect andIndex:k];
[self drawSenterLineWithRect:rect andIndex:k];
}
}
//绘制底部的线条
- (void)drawBottomLineWithRect:(CGRect)rect1 andIndex:(int)k {
CGRect rect = rect1;
float width = (rect.size.width - 16 * (self.numberOfVertificationCode - 1)) / (float)self.numberOfVertificationCode;
float height = rect.size.height;
//1.获取上下文
CGContextRef context = UIGraphicsGetCurrentContext();
//2.设置当前上下问路径
CGFloat lineHidth = 0.5;
CGFloat strokHidth = 0.5;
CGContextSetLineWidth(context, lineHidth);
CGContextSetStrokeColorWithColor(context,BLUE_2.CGColor);
CGContextSetFillColorWithColor(context, BLUE_2.CGColor);
CGRect rectangle = CGRectMake(k * (width + 16), height - strokHidth, width, strokHidth);
CGContextAddRect(context, rectangle);
CGContextStrokePath(context);
}
// 绘制中间的输入的线条
- (void)drawSenterLineWithRect:(CGRect)rect1 andIndex:(int)k{
if (k == self.numberOfVertificationCode - 1
&& self.text.length == self.numberOfVertificationCode) {
// 最后一位且有字
[self.lastShapeLayer removeFromSuperlayer];
self.lastShapeLayer = nil;
} else if (k == self.text.length) {
// 将光标添加在下一个要输入的输入框上
[self.lastShapeLayer removeFromSuperlayer];
self.lastShapeLayer = nil;
CGRect rect = CGRectMake(0,0,208,48);
float width = (rect.size.width - 16 * (self.numberOfVertificationCode - 1)) / (float)self.numberOfVertificationCode;
float height = rect.size.height;
UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(k * (width + 16) + (width - 1.0)/2.0, height/5, 1, height/2)];
CAShapeLayer *line = [CAShapeLayer layer];
line.path = path.CGPath;
line.fillColor = WHITE_1.CGColor;
line.strokeColor = WHITE_1.CGColor;
[line addAnimation:[self opacityAnimation]
forKey:@"kOpacityAnimation"];
[self.layer addSublayer:line];
self.lastShapeLayer = line;
}
}
// 添加光标动画
- (CABasicAnimation *)opacityAnimation {
CABasicAnimation *opacityAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"];
opacityAnimation.fromValue = @(1.0);
opacityAnimation.toValue = @(0.0);
opacityAnimation.duration = 0.9;
opacityAnimation.repeatCount = HUGE_VALF;
opacityAnimation.removedOnCompletion = YES;
opacityAnimation.fillMode = kCAFillModeForwards;
opacityAnimation.timingFunction=[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
return opacityAnimation;
}
@end
YCVerificationCodeInputView
#import
@interface YCVerificationCodeInputView : UIView
/**
验证码的位数
*/
@property (nonatomic, assign) NSInteger numberOfVertificationCode;
/**
验证码
*/
@property (nonatomic, copy) NSString *vertificationCode;
- (void)becomeFirstResponder;
@end
YCVerificationCodeInputView
#import "YCVerificationCodeInputView.h"
#pragma mark - view
#import "YCVerificationCodeInputLabel.h"
@interface YCVerificationCodeInputView ()
/**用于获取键盘输入的内容,实际不显示*/
@property (nonatomic, strong) UITextField *textField;
/**实际用于显示验证码/密码的label*/
@property (nonatomic, strong) YCVerificationCodeInputLabel *label;
@end
@implementation YCVerificationCodeInputView
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
// 设置透明背景色
self.backgroundColor = [UIColor clearColor];
// 设置验证码的位数默认为四位
self.numberOfVertificationCode = 4;
/* 调出键盘的textField */
self.textField = [[UITextField alloc] initWithFrame:self.bounds];
// 隐藏textField,通过点击IDVertificationCodeInputView使其成为第一响应者,来弹出键盘
self.textField.hidden = YES;
self.textField.keyboardType = UIKeyboardTypeNumberPad;
self.textField.delegate = self;
// 将textField放到最后边
[self insertSubview:self.textField atIndex:0];
/* 添加用于显示验证码的label */
self.label = [[YCVerificationCodeInputLabel alloc] initWithFrame:self.bounds];
self.label.numberOfVertificationCode = self.numberOfVertificationCode;
self.label.font = [UIFont fontWithName:BOLD_FONT size:24];
self.label.textColor = WHITE_1;
[self addSubview:self.label];
}
return self;
}
- (void)setNumberOfVertificationCode:(NSInteger)numberOfVertificationCode {
_numberOfVertificationCode = numberOfVertificationCode;
// 保持label的验证码/密码位数与IDVertificationCodeInputView一致,此时label一定已经被创建
self.label.numberOfVertificationCode = numberOfVertificationCode;
}
- (void)becomeFirstResponder{
[self.textField becomeFirstResponder];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self.textField becomeFirstResponder];
}
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
// 判断是不是“删除”字符
if (string.length != 0) { //不是“删除”字符
// 判断验证码/密码的位数是否达到预定的位数
if (textField.text.length < self.numberOfVertificationCode) {
self.label.text = [textField.text stringByAppendingString:string];
self.vertificationCode = self.label.text;
if (self.label.text.length == self.numberOfVertificationCode) {
NSLog(@"tag 已经输入完成验证码了vertificationCode= %@",_vertificationCode);
}
return YES;
} else {
return NO;
}
} else { //是“删除”字符
self.label.text = [textField.text substringToIndex:textField.text.length -1];
self.vertificationCode = self.label.text;
return YES;
}
}
@end
具体使用姿势:
YCEntranceVerificationVC
- (void)viewDidLoad {
[super viewDidLoad];
xxx...
[self configUI];
[self bindingData];
[self.inputView becomeFirstResponder];
}
- (void)configUI {
xxx...
_inputView = [[YCVerificationCodeInputView alloc] initWithFrame:CGRectMake(self.phoneNumberLabel.yc_centerX, self.phoneNumberLabel.yc_bottom + 24, 208, 48)];
[self.view addSubview:self.inputView];
xxx...
}
- (void)bindingData {
@weakify(self);
// 监听完整的验证码
RAC(self.viewModel, captcha) = RACObserve(self, currentCaptcha);
// 监听验证码的输入,输入完成自动发送验证
[[RACObserve(self.inputView, vertificationCode) map:^id(id value) {
@strongify(self);
NSString *vertificationCode = value;
if (vertificationCode.length == 4) {
self.currentCaptcha = vertificationCode;
return @YES;
} else {
return @NO;
}
}] subscribeNext:^(NSNumber *x) {
@strongify(self);
if (x.boolValue) {
// 请求服务器验证验证码
xxx...
}