如何给TextView添加占位字段,看这篇就够了(各种方法集合)


前言:在开发过程当中,我们很多时候会遇到在textView中像textFiled用到占位符的情况,也就是一个placeHold。但是系统并没有给textView提供这个属性,所以我们需要自己来实现。正好写项目遇到这种情况,就把我知道的几种能实现的方式都实现了出来。

  • 第一种方式 简单粗暴

这种方法的特点是,当用户点击了TextView的,占位符占位文字就会立马消失,官方的占位符是当系统监听到用户输入了文字后占位符才会消失.

//    1.把UITextView的文本属性当成“placeholder”使用
//    2.在开始编辑的代理方法里清除“placeholder”
//    3.在结束编辑的代理方法里根据条件设置“placeholder”。

 // 创建textView
    UITextView *textView =[[UITextView alloc]initWithFrame:CGRectMake(20,70,[UIScreen mainScreen].bounds.size.width-40,200)];
    textView.backgroundColor= [UIColor grayColor];
    textView.text = @"这种方法的特点是,当用户点击了TextView的,占位符占位文字就会立马消失,官方的占位符是当系统监听到用户输入了文字后占位符才会消失";
    textView.textColor = [UIColor lightGrayColor];
    textView.delegate = self;
    [self.view addSubview:textView];

代理方法:

#pragma mark - UITextViewDelegate
- (void)textViewDidEndEditing:(UITextView *)textView
{
    if(textView.text.length < 1){
        textView.text = @"这种方法的特点是,当用户点击了TextView的,占位符占位文字就会立马消失,官方的占位符是当系统监听到用户输入了文字后占位符才会消失";
        textView.textColor = [UIColor grayColor];
    }
}
- (void)textViewDidBeginEditing:(UITextView *)textView
{
    if([textView.text isEqualToString:@"这种方法的特点是,当用户点击了TextView的,占位符占位文字就会立马消失,官方的占位符是当系统监听到用户输入了文字后占位符才会消失"]){
        textView.text=@"";
        textView.textColor=[UIColor blackColor];
    }
}
  • 第二种

该方法同样也可以实现类似于占位符的功能相比较方法一,方法二可以实现动态监听文本的改变,并非弹出键盘就立即清除占位符,只有当用户开始输入文本的时候.placeholder才会消失。同样,当用户清空文本的时候,占位符又会重新显示出来。

这种方法最致命的弊端在于要设置textView的偏移来设置label的位置,不好控制

//    1.创建textView
    UITextView *textView = [[UITextView alloc] initWithFrame:CGRectMake(10, 74, [UIScreen mainScreen].bounds.size.width - 20, 200)];
    textView.backgroundColor = [UIColor grayColor];
    
    [self.view addSubview:textView];
    self.textView = textView;
    self.textView.delegate = self;
    
    textView.contentInset = UIEdgeInsetsMake(-30, 0, 0, 0);

//    2.给textView添加一个UILabel子控件,作为占位符
    UILabel *placeHolder = [[UILabel alloc] initWithFrame:CGRectMake(15, 0, [UIScreen mainScreen].bounds.size.width - 50, 200)];
    self.placeHolder = placeHolder;
    placeHolder.text = @"该方法同样也可以实现类似于占位符的功能相比较方法一,方法二可以实现动态监听文本的改变,并非弹出键盘就立即清除占位符,只有当用户开始输入文本的时候.placeholder才会消失。同样,当用户清空文本的时候,占位符又会重新显示出来。";
    placeHolder.textColor = [UIColor lightGrayColor];
    placeHolder.numberOfLines = 0;
    placeHolder.contentMode = UIViewContentModeTop;
    [self.textView addSubview:placeHolder];

代理方法

#pragma mark - UITextViewDelegate
- (void)textViewDidChange:(UITextView *)textView
{
    if (!textView.text.length) {
        self.placeHolder.alpha = 1;
    } else {
        self.placeHolder.alpha = 0;
    }
}
  • 第三种

相比计较上面两种方法,这种方法可移植性、拓展性更好,这种方法,不仅乐意随意通过我们添加的placeholder属性设置默认文字,还可以通过我们添加的placeholderColor设置默认文字的颜色。今后,我们只需要写好这么一个自定义UITextView,·就可以一劳永逸。

// 1.自定义UITextView(继承UITextView)
// 2.给UITextView添加placeholder和placeholderColor属性
// 3.重写initWithFrame方法
// 4.添加通知监听文字改变
// 5.重写drawRect:方法
// 6.重写相关属性的set方法

@interface SYJThirdTextView : UITextView
/** 占位文字 */
@property (nonatomic, copy) NSString *placeholder;
/** 占位文字颜色 */
@property (nonatomic, strong) UIColor *placeholderColor;
@end

#.m中实现

- (instancetype)initWithFrame:(CGRect)frame
{
    if (self = [super initWithFrame:frame]) {
        // 设置默认字体
        self.font = [UIFont systemFontOfSize:15];
        // 设置默认颜色
        self.placeholderColor = [UIColor grayColor];
        
        // 使用通知监听文字改变
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textDidChange:) name:UITextViewTextDidChangeNotification object:self];
    }
    return self;
}

- (void)textDidChange:(NSNotification *)note
{
    // 会重新调用drawRect:方法
    [self setNeedsDisplay];
}

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

/**
 * 每次调用drawRect:方法,都会将以前画的东西清除掉
 */
- (void)drawRect:(CGRect)rect
{
    // 如果有文字,就直接返回,不需要画占位文字
    if (self.hasText) return;
    
    // 属性
    NSMutableDictionary *attrs = [NSMutableDictionary dictionary];
    attrs[NSFontAttributeName] = self.font;
    attrs[NSForegroundColorAttributeName] = self.placeholderColor;
    
    // 画文字
    rect.origin.x = 5;
    rect.origin.y = 8;
    rect.size.width -= 2 * rect.origin.x;
    [self.placeholder drawInRect:rect withAttributes:attrs];
}

- (void)layoutSubviews
{
    [super layoutSubviews];
    
    [self setNeedsDisplay];
}

#pragma mark - setter
- (void)setPlaceholder:(NSString *)placeholder
{
    _placeholder = [placeholder copy];
    
    [self setNeedsDisplay];
}

- (void)setPlaceholderColor:(UIColor *)placeholderColor
{
    _placeholderColor = placeholderColor;
    
    [self setNeedsDisplay];
}

- (void)setFont:(UIFont *)font
{
    [super setFont:font];
    
    [self setNeedsDisplay];
}

- (void)setText:(NSString *)text
{
    [super setText:text];
    
    [self setNeedsDisplay];
}

- (void)setAttributedText:(NSAttributedString *)attributedText
{
    [super setAttributedText:attributedText];
    
    [self setNeedsDisplay];
}

    SYJThirdTextView *textView = [[SYJThirdTextView alloc]init];
    [self.view addSubview:textView];
    textView.frame = CGRectMake(10, 100, [UIScreen mainScreen].bounds.size.width - 20, 240);
    textView.placeholder = @"相比计较上面两种方法,这种方法可移植性、拓展性更好,这种方法,不仅乐意随意通过我们添加的placeholder属性设置默认文字,还可以通过我们添加的placeholderColor设置默认文字的颜色。今后,我们只需要写好这么一个自定义UITextView,就可以一劳永逸。";
    textView.backgroundColor = [UIColor grayColor];
    //占位符颜色
    textView.placeholderColor = [UIColor lightGrayColor];
  • 第四种

这个方法的和方法三很相似,只是没有利用通知来监听文本的改变,需要配合textViewDidChanged:这个文本改变的代理方法使用。

@interface SYJFourTextView : UITextView

//1.自定义UITextView
//2.给UITextView添加placeholder和placeholderColor属性
//3.重写initWithFrame方法
//4.重写drawRect:方法
//5.重写相关属性的set方法

/** 占位文字 */
@property (nonatomic,copy) NSString *placeholder;
/** 占位文字颜色 */
@property (nonatomic,strong) UIColor *placeholderColor;

@end

- (instancetype)initWithFrame:(CGRect)frame
{
    if (self = [super initWithFrame:frame]) {
        self.font = [UIFont systemFontOfSize:15];
        self.placeholderColor = [UIColor lightGrayColor];
        self.placeholder = @"请输入内容";
    }
    return self;
}

// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
    // Drawing code
    NSMutableDictionary *attrs = [NSMutableDictionary dictionary];
    attrs[NSFontAttributeName] = self.font;
    attrs[NSForegroundColorAttributeName] = self.placeholderColor;
    
    [self.placeholder drawInRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height) withAttributes:attrs];
}

// 布局子控件的时候需要重绘
- (void)layoutSubviews
{
    [super layoutSubviews];
    [self setNeedsDisplay];
    
}
// 设置属性的时候需要重绘,所以需要重写相关属性的set方法
- (void)setPlaceholder:(NSString *)placeholder
{
    _placeholder = placeholder;
    [self setNeedsDisplay];
}

- (void)setPlaceholderColor:(UIColor *)placeholderColor
{
    _placeholderColor = placeholderColor;
    [self setNeedsDisplay];
    
}

- (void)setFont:(UIFont *)font
{
    [super setFont:font];
    [self setNeedsDisplay];
}

- (void)setText:(NSString *)text
{
    [super setText:text];
    if (text.length) {
        
    // 因为是在文本改变的代理方法中判断是否显示placeholder,而通过代码设置text的方式又不会调用文本改变的代理方法,所以再此根据text是否不为空判断是否显示placeholder。
        self.placeholder = @"";
    }
    [self setNeedsDisplay];
}

- (void)setAttributedText:(NSAttributedString *)attributedText
{
    [super setAttributedText:attributedText];
    if (attributedText.length) {
        self.placeholder = @"";
    }
    [self setNeedsDisplay];
}

SYJFourTextView *textView = [[SYJFourTextView alloc] initWithFrame:CGRectMake(10, 80, self.view.frame.size.width - 20, 240)];
    textView.backgroundColor = [UIColor grayColor];
    textView.placeholder = @"这个方法的和方法三很相似,只是没有利用通知来监听文本的改变,需要配合textViewDidChanged:这个文本改变的代理方法使用。";
    textView.placeholderColor = [UIColor lightGrayColor];
    textView.delegate = self;
    [self.view addSubview:textView];

#pragma mark - UITextViewDelegate
- (void)textViewDidChange:(SYJFourTextView *)textView // 此处取巧,把代理方法参数类型直接改成自定义的SYJFourTextView类型,为了可以使用自定义的placeholder属性,省去了通过给控制器SYJFourTextView类型属性这样一步。
{
    if (textView.hasText) { // textView.text.length
        textView.placeholder = @"";
        
    } else {
        textView.placeholder = @"这个方法的和方法三很相似,只是没有利用通知来监听文本的改变,需要配合textViewDidChanged:这个文本改变的代理方法使用";
        
    }
}

  • 第五种 <推荐>

相对于上面的4种方法,这种方法更加取巧,虽然Apple官方没有给我们开发者提供类似于placeholder的属性,但是通过运行时,我们遍历出了一个placeHolderLabel的私有变量。这种方法简单易懂,代码量少,推荐大家使用这种方法。

//    通过runtime,我们发现,UITextView内部有一个名为“_placeHolderLabel”的私有成员变量。大家知道,Objective-C没有绝对的私有变量,因为我们可以通过KVC来访问私有变量。

    UITextView *textView = [[UITextView alloc] initWithFrame:CGRectMake(10, 100, [UIScreen mainScreen].bounds.size.width -20, 240)];
    [textView setBackgroundColor:[UIColor greenColor]];
    [self.view addSubview:textView];
    
    // _placeholderLabel
    UILabel *placeHolderLabel = [[UILabel alloc] init];
    placeHolderLabel.text = @"相对于上面的4种方法,这种方法更加取巧,虽然Apple官方没有给我们开发者提供类似于placeholder的属性,但是通过运行时,我们遍历出了一个placeHolderLabel的私有变量。这种方法简单易懂,代码量少,推荐大家使用这种方法。";
    placeHolderLabel.numberOfLines = 0;
    placeHolderLabel.textColor = [UIColor lightGrayColor];
    [placeHolderLabel sizeToFit];
    [textView addSubview:placeHolderLabel];
    
    // same font
    textView.font = [UIFont systemFontOfSize:13.f];
    placeHolderLabel.font = [UIFont systemFontOfSize:13.f];
    
    [textView setValue:placeHolderLabel forKey:@"_placeholderLabel"];
                
  • 第六种(高大上)

这种方法就是比较高大上的一种了,也是第五种深入思考。既然我们可以通过runtime找到一个属性,那么我们也可以通过runtime给他动态添加属性。可以为TextView分类动态添加属性,无需继承,用法方便。

@interface UITextView (SYJText)

/**
 *  UITextView+placeholder
 */
@property (nonatomic, copy) NSString *syj_placeHolder;
/**
 *  IQKeyboardManager等第三方框架会读取placeholder属性并创建UIToolbar展示
 */
@property (nonatomic, copy) NSString *placeholder;
/**
 *  placeHolder颜色
 */
@property (nonatomic, strong) UIColor *syj_placeHolderColor;


@end

#.m

static const void *syj_placeHolderKey;
@interface UITextView ()
@property (nonatomic, readonly) UILabel *syj_placeHolderLabel;
@end

@implementation UITextView (SYJText)

+(void)load{
    [super load];
    
    //方法交换
    method_exchangeImplementations(class_getInstanceMethod(self.class, NSSelectorFromString(@"layoutSubviews")),
                                   class_getInstanceMethod(self.class, @selector(SYJPlaceHolder_swizzling_layoutSubviews)));
    method_exchangeImplementations(class_getInstanceMethod(self.class, NSSelectorFromString(@"dealloc")),
                                   class_getInstanceMethod(self.class, @selector(SYJPlaceHolder_swizzled_dealloc)));
    method_exchangeImplementations(class_getInstanceMethod(self.class, NSSelectorFromString(@"setText:")),
                                   class_getInstanceMethod(self.class, @selector(SYJPlaceHolder_swizzled_setText:)));
}

#pragma mark - swizzling

- (void)SYJPlaceHolder_swizzled_dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    [self SYJPlaceHolder_swizzled_dealloc];
}

- (void)SYJPlaceHolder_swizzling_layoutSubviews{
    if (self.syj_placeHolder) {
        UIEdgeInsets textContainerInset = self.textContainerInset;
        CGFloat lineFragmentPadding = self.textContainer.lineFragmentPadding;
        CGFloat x = lineFragmentPadding + textContainerInset.left + self.layer.borderWidth;
        CGFloat y = textContainerInset.top + self.layer.borderWidth;
        CGFloat width = CGRectGetWidth(self.bounds) - x - textContainerInset.right - 2*self.layer.borderWidth;
        CGFloat height = [self.syj_placeHolderLabel sizeThatFits:CGSizeMake(width, 0)].height;
        self.syj_placeHolderLabel.frame = CGRectMake(x, y, width, height);
    }
    [self SYJPlaceHolder_swizzling_layoutSubviews];
}
- (void)SYJPlaceHolder_swizzled_setText:(NSString *)text{
    [self SYJPlaceHolder_swizzled_setText:text];
    if (self.syj_placeHolder) {
        [self updatePlaceHolder];
    }
}
#pragma mark - associated

//动态添加属性
//绑定syj_placeHolder属性,作为textview的占位符
-(NSString *)syj_placeHolder{
    return objc_getAssociatedObject(self, &syj_placeHolderKey);
}
-(void)setSyj_placeHolder:(NSString *)syj_placeHolder{
    objc_setAssociatedObject(self, &syj_placeHolderKey, syj_placeHolder, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    [self updatePlaceHolder];
}
-(UIColor *)syj_placeHolderColor{
    return self.syj_placeHolderLabel.textColor;
}
-(void)setSyj_placeHolderColor:(UIColor *)syj_placeHolderColor{
    self.syj_placeHolderLabel.textColor = syj_placeHolderColor;
}
-(NSString *)placeholder{
    return self.syj_placeHolder;
}
-(void)setPlaceholder:(NSString *)placeholder{
    self.syj_placeHolder = placeholder;
}

#pragma mark - update
- (void)updatePlaceHolder{
    if (self.text.length) {
        [self.syj_placeHolderLabel removeFromSuperview];
        return;
    }
    self.syj_placeHolderLabel.font = self.font?self.font:self.cacutDefaultFont;
    self.syj_placeHolderLabel.textAlignment = self.textAlignment;
    self.syj_placeHolderLabel.text = self.syj_placeHolder;
    [self insertSubview:self.syj_placeHolderLabel atIndex:0];
}

#pragma mark - lazzing
-(UILabel *)syj_placeHolderLabel{
    UILabel *placeHolderLab = objc_getAssociatedObject(self, @selector(syj_placeHolderLabel));
    if (!placeHolderLab) {
        placeHolderLab = [[UILabel alloc] init];
        placeHolderLab.numberOfLines = 0;
        placeHolderLab.textColor = [UIColor lightGrayColor];
        objc_setAssociatedObject(self, @selector(syj_placeHolderLabel), placeHolderLab, OBJC_ASSOCIATION_RETAIN);
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updatePlaceHolder) name:UITextViewTextDidChangeNotification object:self];
    }
    return placeHolderLab;
}
- (UIFont *)cacutDefaultFont{
    static UIFont *font = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        UITextView *textview = [[UITextView alloc] init];
        textview.text = @" ";
        font = textview.font;
    });
    return font;
}


@end

 //利用runtime黑魔法及动态添加属性对textview添加placeHolder属性
    UITextView *text = [[UITextView alloc]initWithFrame:CGRectMake(10, 100, [UIScreen mainScreen].bounds.size.width - 20, 240)];
    [self.view addSubview:text];
    text.backgroundColor = [UIColor grayColor];
    //设置大小要在font设置place前边
    text.font = [UIFont boldSystemFontOfSize:16];
    text.syj_placeHolder = @"这个方法更加的友好,只需引入头文件,所有文件就能直接使用占位符,不用集成其他类,建议使用这种方法";
    text.syj_placeHolderColor = [UIColor lightGrayColor];

看下各种的效果

TextView占位符

代码上传到github,有需要可以看一下。
https://github.com/SYJshang/TextViewPlaceHolder

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

推荐阅读更多精彩内容