You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为JVFloatLabeledTextField实现类似SkyFloatingLabelTextField的动态下划线?

实现JVFloatLabeledTextField的动态下划线效果

嘿,我之前也折腾过用JVFloatLabeledTextField实现类似SkyFloatingLabelTextField的动态下划线,给你分享两个实用的方案,直接基于现有库扩展就行,不用换库~

方案一:基础动态下划线(颜色+高度过渡)

这个方案实现编辑时下划线变粗变色、结束编辑时根据文本状态恢复的效果,代码简单易上手:

步骤1:自定义JVFloatLabeledTextField子类

创建一个子类,封装下划线的逻辑,避免污染ViewController:

#import "JVFloatLabeledTextField.h"

@interface CustomFloatTextField : JVFloatLabeledTextField
@property (nonatomic, strong) CALayer *bottomBorderLayer;
@end

@implementation CustomFloatTextField

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

// 初始化下划线和事件监听
- (void)setupBottomBorder {
    // 移除默认边框,避免和自定义下划线冲突
    self.borderStyle = UITextBorderStyleNone;
    
    // 创建基础下划线layer
    self.bottomBorderLayer = [CALayer layer];
    CGFloat initialHeight = 1.0;
    self.bottomBorderLayer.frame = CGRectMake(0, self.frame.size.height - initialHeight, self.frame.size.width, initialHeight);
    self.bottomBorderLayer.backgroundColor = [UIColor lightGrayColor].CGColor;
    [self.layer addSublayer:self.bottomBorderLayer];
    
    // 监听编辑状态变化
    [self addTarget:self action:@selector(onEditingBegin:) forControlEvents:UIControlEventEditingDidBegin];
    [self addTarget:self action:@selector(onEditingEnd:) forControlEvents:UIControlEventEditingDidEnd];
}

// 开始编辑时的动画:变粗+变色
- (void)onEditingBegin:(UITextField *)textField {
    // 颜色动画
    CABasicAnimation *colorAnim = [CABasicAnimation animationWithKeyPath:@"backgroundColor"];
    colorAnim.fromValue = (__bridge id)[UIColor lightGrayColor].CGColor;
    colorAnim.toValue = (__bridge id)[UIColor systemBlueColor].CGColor;
    colorAnim.duration = 0.3;
    
    // 高度动画
    CABasicAnimation *heightAnim = [CABasicAnimation animationWithKeyPath:@"bounds.size.height"];
    heightAnim.fromValue = @1.0;
    heightAnim.toValue = @2.0;
    heightAnim.duration = 0.3;
    
    [self.bottomBorderLayer addAnimation:colorAnim forKey:@"colorTransition"];
    [self.bottomBorderLayer addAnimation:heightAnim forKey:@"heightTransition"];
    
    // 更新layer最终状态(动画只是视觉效果,必须手动更新属性)
    self.bottomBorderLayer.backgroundColor = [UIColor systemBlueColor].CGColor;
    self.bottomBorderLayer.frame = CGRectMake(0, self.frame.size.height - 2.0, self.frame.size.width, 2.0);
}

// 结束编辑时的状态恢复
- (void)onEditingEnd:(UITextField *)textField {
    if (textField.text.length == 0) {
        // 文本为空,恢复初始样式
        CABasicAnimation *colorAnim = [CABasicAnimation animationWithKeyPath:@"backgroundColor"];
        colorAnim.fromValue = (__bridge id)[UIColor systemBlueColor].CGColor;
        colorAnim.toValue = (__bridge id)[UIColor lightGrayColor].CGColor;
        colorAnim.duration = 0.3;
        
        CABasicAnimation *heightAnim = [CABasicAnimation animationWithKeyPath:@"bounds.size.height"];
        heightAnim.fromValue = @2.0;
        heightAnim.toValue = @1.0;
        heightAnim.duration = 0.3;
        
        [self.bottomBorderLayer addAnimation:colorAnim forKey:@"colorReverse"];
        [self.bottomBorderLayer addAnimation:heightAnim forKey:@"heightReverse"];
        
        self.bottomBorderLayer.backgroundColor = [UIColor lightGrayColor].CGColor;
        self.bottomBorderLayer.frame = CGRectMake(0, self.frame.size.height - 1.0, self.frame.size.width, 1.0);
    } else {
        // 保留高亮颜色,仅恢复高度
        CABasicAnimation *heightAnim = [CABasicAnimation animationWithKeyPath:@"bounds.size.height"];
        heightAnim.fromValue = @2.0;
        heightAnim.toValue = @1.0;
        heightAnim.duration = 0.3;
        
        [self.bottomBorderLayer addAnimation:heightAnim forKey:@"heightReverse"];
        self.bottomBorderLayer.frame = CGRectMake(0, self.frame.size.height - 1.0, self.frame.size.width, 1.0);
    }
}

// 适配屏幕旋转或frame变化
- (void)layoutSubviews {
    [super layoutSubviews];
    CGFloat borderHeight = (self.isEditing || self.text.length > 0) ? 2.0 : 1.0;
    self.bottomBorderLayer.frame = CGRectMake(0, self.frame.size.height - borderHeight, self.frame.size.width, borderHeight);
}

@end

步骤2:替换原有JVFloatLabeledTextField

在你的项目中,把原本的JVFloatLabeledTextField实例替换成CustomFloatTextField即可,不管是代码创建还是xib/storyboard里修改类名。


方案二:滑动填充式下划线(更贴近Sky的效果)

如果想要实现SkyFloatingLabelTextField里那种下划线从左到右填充颜色的动画,可以用Layer Mask来实现:

#import "JVFloatLabeledTextField.h"

@interface CustomFloatTextField : JVFloatLabeledTextField
@property (nonatomic, strong) CALayer *defaultBorderLayer; // 初始灰色下划线
@property (nonatomic, strong) CALayer *activeBorderLayer; // 高亮蓝色下划线
@property (nonatomic, strong) CALayer *borderMaskLayer;   // 用于动画的遮罩层
@end

@implementation CustomFloatTextField

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

- (void)setupAnimatedBorder {
    self.borderStyle = UITextBorderStyleNone;
    
    // 1. 初始灰色下划线
    self.defaultBorderLayer = [CALayer layer];
    self.defaultBorderLayer.frame = CGRectMake(0, self.frame.size.height - 1.0, self.frame.size.width, 1.0);
    self.defaultBorderLayer.backgroundColor = [UIColor lightGrayColor].CGColor;
    [self.layer addSublayer:self.defaultBorderLayer];
    
    // 2. 高亮蓝色下划线(初始被遮罩隐藏)
    self.activeBorderLayer = [CALayer layer];
    self.activeBorderLayer.frame = CGRectMake(0, self.frame.size.height - 2.0, self.frame.size.width, 2.0);
    self.activeBorderLayer.backgroundColor = [UIColor systemBlueColor].CGColor;
    [self.layer addSublayer:self.activeBorderLayer];
    
    // 3. 遮罩层:初始宽度为0,只显示activeBorderLayer的左侧一小部分
    self.borderMaskLayer = [CALayer layer];
    self.borderMaskLayer.backgroundColor = [UIColor whiteColor].CGColor;
    self.borderMaskLayer.frame = CGRectMake(0, 0, 0, 2.0);
    self.activeBorderLayer.mask = self.borderMaskLayer;
    
    // 监听编辑事件
    [self addTarget:self action:@selector(onEditingBegin:) forControlEvents:UIControlEventEditingDidBegin];
    [self addTarget:self action:@selector(onEditingEnd:) forControlEvents:UIControlEventEditingDidEnd];
}

- (void)onEditingBegin:(UITextField *)textField {
    self.defaultBorderLayer.hidden = YES;
    
    // 动画遮罩层宽度从0到全屏,实现从左到右填充的效果
    CABasicAnimation *widthAnim = [CABasicAnimation animationWithKeyPath:@"bounds.size.width"];
    widthAnim.fromValue = @0;
    widthAnim.toValue = @(self.frame.size.width);
    widthAnim.duration = 0.4;
    
    [self.borderMaskLayer addAnimation:widthAnim forKey:@"maskExpand"];
    self.borderMaskLayer.frame = CGRectMake(0, 0, self.frame.size.width, 2.0);
}

- (void)onEditingEnd:(UITextField *)textField {
    if (textField.text.length == 0) {
        // 文本为空,动画遮罩层收缩回0,然后显示初始下划线
        CABasicAnimation *widthAnim = [CABasicAnimation animationWithKeyPath:@"bounds.size.width"];
        widthAnim.fromValue = @(self.frame.size.width);
        widthAnim.toValue = @0;
        widthAnim.duration = 0.4;
        widthAnim.delegate = self;
        
        [self.borderMaskLayer addAnimation:widthAnim forKey:@"maskShrink"];
        self.borderMaskLayer.frame = CGRectMake(0, 0, 0, 2.0);
    } else {
        // 保留高亮下划线,遮罩层保持全屏
        self.borderMaskLayer.frame = CGRectMake(0, 0, self.frame.size.width, 2.0);
    }
}

// 动画结束后恢复初始下划线显示
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
    if (flag && [anim isEqual:[self.borderMaskLayer animationForKey:@"maskShrink"]]) {
        self.defaultBorderLayer.hidden = NO;
    }
}

- (void)layoutSubviews {
    [super layoutSubviews];
    // 更新各layer的frame以适配布局变化
    self.defaultBorderLayer.frame = CGRectMake(0, self.frame.size.height - 1.0, self.frame.size.width, 1.0);
    self.activeBorderLayer.frame = CGRectMake(0, self.frame.size.height - 2.0, self.frame.size.width, 2.0);
    self.borderMaskLayer.frame = CGRectMake(0, 0, (self.isEditing || self.text.length > 0) ? self.frame.size.width : 0, 2.0);
}

@end

注意事项

  • 记得将所有JVFloatLabeledTextField的borderStyle设为UITextBorderStyleNone,避免默认边框和自定义下划线冲突;
  • 动画的颜色、时长、下划线高度都可以根据你的UI设计自由调整;
  • 如果需要适配多语言或动态字体,记得在layoutSubviews里同步更新layer的状态;

内容的提问来源于stack exchange,提问作者SwiftNinja95

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 07:30:41