在Cocoa Touch框架中有两种系统自带的文本输入控件:UITextFiled
和UITextView
。
- UITextFiled特点
- 只显示一行文本输入
- 高度(30)默认不能修改
- 没有拖拽滚动(父控件是UIscrollView除外)
- UITextView特点
- 支持多行输入显示
- 支持滚动(继承自UIscrollView)
- 没有占位文字(placeholder)
- 本质就是一个可调整的多行输入文本框
有时候我们希望给一个多行文本框增加一个占位文字的功能,来提示用户输入的内容。但是这两种都不能满足我们的要求,这样我们可以自定义一个这样的控件,实现我们的需要。
实现思路:
自定义一个类,继承自UITextView,在现有功能的基础上增加一个占位文字的功能
两种实现方法:
1. 实现UITextView内部的- (void)drawRect:(CGRect)rect
方法,将占位文字文字画到UITextView控件上
1.1 在.h的头文件中给外界提供一个占位文字的属性
/** * 占位文字 */@property (weak, nonatomic) NSString *placeholder;复制代码
1.2 将占位文字画到矩形框中
/** * 绘制占位文字 */- (void)drawRect:(CGRect)rect { NSMutableDictionary *attrs = [NSMutableDictionary dictionary]; attrs[NSFontAttributeName] = self.font; attrs[NSForegroundColorAttributeName] = [UIColor lightGrayColor]; // 在textView的矩形框中绘制文字 [self.placeholder drawInRect:CGRectMake(0, 64, self.frame.size.width, self.frame.size.height) withAttributes:attrs];}复制代码
- 注:label的font要在初始化时确定,不然程序会奔溃
1.3 采用通知的方式监听键盘文字的改变。文字一旦改变,会发出一个UITextViewTextDidChangeNotification
的通知,所以给TextView初始化后就添加一个监听器
- (instancetype)initWithFrame:(CGRect)frame{ if (self = [super initWithFrame:frame]) { // 添加监听器,监听自己的文字改变通知 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textDidChange) name:UITextViewTextDidChangeNotification object:nil]; } return self;}// 时刻监听文字键盘文字的变化,文字一旦改变便调用setNeedsDisplay方法- (void)textDidChange{ // 该方法会调用drawRect:方法,立即重新绘制占位文字 [self setNeedsDisplay];}复制代码
1.4 添加监听器就要重写dealloc方法,当控件被销毁时,移除监听器
- (void)dealloc{ [[NSNotificationCenter defaultCenter] removeObserver:self];}复制代码
1.5 用户一旦输入了文字,就需要将绘制的占位文字删除掉,所以在drawRect:方法一进来就增加判断
// 如果一旦有输入文字,直接返回,不再绘制占位文字if (self.hasText) return;复制代码
因为drawRect:方法是每次调用会将上次绘制的内容删除掉,重新绘制,所以增加一个这样的判断,当有文字时,再重绘时直接返回,不进行后面的绘制,这样就可以办到有文字输入后,删除占位文字
但其实还有一些问题,控制器中拿到这个textView控件后,可能会随时修改占位文字的字体大小、占位文字的内容,所以避免这些问题,随时响应修改,就需要重写这些属性的setter方法 1.6 重写字体、占位文字等属性的setter方法
// 占位文字的setter方法- (void)setPlaceholder:(NSString *)placeholder{ _placeholder = placeholder; // 文字一旦改变,立马重写绘制(内部会调drawRect:方法) [self setNeedsDisplay]; }// 字体属性setter方法- (void)setFont:(UIFont *)font{ [super setFont:font]; [self setNeedsDisplay];}复制代码
这样,这个在系统自带控件基础上自定义的UITextView控件就有占位文字功能了。
2.给自定TextView控件增加一个label属性,用来显示占位文字
这里,相同的部分就不在贴代码,只阐述实现步骤和思路 2.1 在.m文件类扩展增加一个label属性,(写在类扩展中是为了将子控件私有化,不暴漏在外部让被人随意修改属性值)
/** * 占位文字Label */@property (weak, nonatomic) UILabel *phLabel;复制代码
2.2 重写getter方法,采用懒加载,不用关心控件的创建时间,用到时自动加载,同时初始化一些属性
- (UILabel *)phLabel{ if (!_phLabel) { UILabel *phLabel = [[UILabel alloc] init]; // 文字自动换行 phLabel.numberOfLines = 0; phLabel.x = 4; phLabel.y = 7; [self addSubview:phLabel]; self.phLabel = phLabel; } return _phLabel;}复制代码
2.3 给控件添加监听器,代码同上1.3 2.4 重写dealloc方法,移除监听器,同上1.4 2.5 在- (void)textDidChange方法中控制label的显示或隐藏
- (void)textDidChange{ // 有文字就隐藏 self.phLabel.hidden = self.hasText;}复制代码
2.6 在- (void)layoutSubviews方法中动态计算占位文字的label大小
- (void)layoutSubviews{ [super layoutSubviews]; // 确定label的宽度,高度由文字数量自动计算 CGSize size = CGSizeMake(self.width - 2 * self.phLabel.x, MAXFLOAT); // 根据文字的字体属性、文字的数量动态计算label的尺寸 self.phLabel.size = [self.placeholder boundingRectWithSize:size options:NSStringDrawingUsesLineFragmentOrigin attributes:@{ NSFontAttributeName : self.font} context:nil].size;}复制代码
- 注:label的font要在初始化时确定,不然程序会奔溃
2.7 重写字体、占位文字的setter方法(满足控制器中可能随时修改占位文字的尺寸和大小)
// 占位文字- (void)setPlaceholder:(NSString *)placeholder{ _placeholder = placeholder; self.phLabel.text = placeholder; // 更新文字尺寸 [self setNeedsLayout];}// 字体- (void)setFont:(UIFont *)font{ [super setFont:font]; self.phLabel.font = font; [self setNeedsLayout];}复制代码
setNeedsLayout方法会在控件尺寸变化时调用layoutSubviews重新计算子控件的尺寸和大小
这种方式增加的占位文字有一个优势:
占位文字可以跟随光标一起拖拽上下移动(弹簧效果),但利用drawRect:画上去的占位文字是固定的,没有拖拽效果
控制器在拿到这个控件后,设置占位文字后就可以直接用了。