前言:在开发过程当中,我们很多时候会遇到在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
网友评论