简易封装一个带有占位文字的TextView
在实际iOS应用开发中我们经常会用到类似于下图所示的界面,即带有占位文字的文本框:
其实在开发中,我们会自然而然地用到一种思想,就是我们所需求的界面,使用系统自带的控件可以实现其部分功能,但有些功能系统自带的控件无法实现。这个时候我们一般会想到自定义一个控件,继承于系统自带的控件,并给它添加额外的功能。
此时,依据这种思想,根据需求,我们很容易联想到系统自带的UITextField和UITextView。下面我们针对此次需求对该两种控件进行简单地分析:
对于UITextField :
1> 有占位文字
2> 最多只能输入一行文字
对于UITextView :
1> 没有占位文字
2> 能输入任意行文字,并且超出显示范围的可以滚动(UITextView继承自UIScrollView)
而我们需要的结果 :
1> 有占位文字
2> 能输入任意行文字
那么我们可以用脚想一想便可选择自定义控件继承于UITextView,实现添加其占位文字的功能。
要封装一个自定义的控件,以后需要时能够方便地使用它。它的.h文件是至关重要的,在其.h文件里提供一些需要用到的属性作为外界使用它的接口,用到时只需在外界控制器里面创建并给所需要得属性设值,并添加到相应地View上即可,至于自定义的控件内部具体的功能是怎么实现的则都封装在控件内部,外界无需知道。
这里我们首先给这个案例进行简要的分析实现,后续再进一步完善。
在 BSPlaceholderTextView.h文件里提供占位文字及其颜色和字体等外界用到时可自行设置的属性接口
#import <UIKit/UIKit.h> @interface BSPlaceholderTextView : UITextView /** 占位文字 */ @property (nonatomic, copy) NSString *placeholder; /** 占位文字颜色 */ @property (nonatomic, strong) UIColor *placeholderColor; @end
在BSPlaceholderTextView.m文件里实现所需功能.大体思路是:通过drawRect:方法将占位文字画在textView指定位置,使用通知监听文本框文字的改变,只要有改变就调用[self setNeedsDisplay]它会重新调用drawRect:方法重绘,根据drawRect:方法特性,每次调用时都会将之前画得东西清除重新绘制,在方法里面判断textView是否有文字,完成只要用户输入了有文字就直接return,当用户没有输入文字,或者输了文字再完全删除时,就会重新绘制占位文字并显示的逻辑功能,
#import "BSPlaceholderTextView.h" @implementation BSPlaceholderTextView - (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)dealloc { //removeObserver [[NSNotificationCenter defaultCenter] removeObserver:self]; } - (void)textDidChange:(NSNotification *)note { //重绘 //会重新调用drawRect:方法 [self setNeedsDisplay]; } /** * 每次调用drawRect:方法,都会将以前画的东西清除掉,重新画 */ - (void)drawRect:(CGRect)rect { //如果有文字就直接返回,不需要画占位文字 if (self.text.length) return;
//设置占位文字属性 NSMutableDictionary *attrs = [NSMutableDictionary dictionary]; attrs[NSFontAttributeName] = self.font; attrs[NSForegroundColorAttributeName] = self.placeholderColor; //画占位文字 //调整rect:画得位置(5,8是根据经验所得)调整光标,和每一行两边各控制间隙 rect.origin.x = 5; rect.origin.y = 8; rect.size.width -= 2 * rect.origin.x; //画 [self.placeholder drawInRect:rect withAttributes:attrs]; } @end
其中,我们需要注意一个常识性的细节,在导航控制器设置textView.frame = self.view.bounds;时,这个bounds的origin的y值实际上是-64,这与我们常见的控件的bounds一般是以自己的左上角为原点,即其x,y均为0有所出入。事实上,是只要在导航控制器中苹果就会自动帮我们调整了textView的内边距将顶部下移了64(20+44,导航栏高44),具体的如果我们将导航控制器的导航栏隐藏self.navigationController.navigationBarHidden = YES;系统就会自动帮为调整textView的内边距将顶部下移20。知道这些原理了当我们觉得苹果自动帮我们调整是自作多情时,可以如下设置其自动调整属性为NO或手动调整textView的内边距为想要的效果。
// NO:不要自动调整scrollView的contentInet属性 // self.automaticallyAdjustsScrollViewInsets = NO; // textView.contentInset = UIEdgeInsetsMake(-64, 0, 0, 0);
至此,我们所想要的效果基本完成,考虑到我们既然在.h文件了属性给外界使用就有可能会随时在外界修改它,但是drawRect:方法只有在画出来显示的时候生效,显示完了之后再再要更改占位文字便会无效,这时我们采取重写setter,实时监听外界对占位文字属性的改变,并调用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]; }
当然,对于上述问题的解决方案,我们也联想了苹果自带的UITextField的占位文字,模仿它给textView里面添加一个Lable,换汤不换药,思路其实是差不多的,废话不多说直接上代码:
下面是自定义的PlaceholderTextView.m文件
#import "XMGPlaceholderTextView.h" @interface XMGPlaceholderTextView() /** 占位文字label */ @property (nonatomic, weak) UILabel *placeholderLabel; @end @implementation XMGPlaceholderTextView - (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { // 创建label UILabel *placeholderLabel = [[UILabel alloc] init]; placeholderLabel.numberOfLines = 0; [self addSubview:placeholderLabel]; self.placeholderLabel = placeholderLabel; // 设置默认字体 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 { self.placeholderLabel.hidden = self.hasText; } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; } - (void)layoutSubviews { [super layoutSubviews]; self.placeholderLabel.x = 5; self.placeholderLabel.y = 8; self.placeholderLabel.width = self.width - 2 * self.placeholderLabel.x; [self.placeholderLabel sizeToFit]; } #pragma mark - setter - (void)setPlaceholder:(NSString *)placeholder { _placeholder = [placeholder copy]; self.placeholderLabel.text = placeholder; [self.placeholderLabel sizeToFit]; } - (void)setPlaceholderColor:(UIColor *)placeholderColor { _placeholderColor = placeholderColor; self.placeholderLabel.textColor = placeholderColor; } - (void)setFont:(UIFont *)font { [super setFont:font]; self.placeholderLabel.font = font; [self.placeholderLabel sizeToFit]; } - (void)setText:(NSString *)text { [super setText:text]; self.placeholderLabel.hidden = self.hasText; } - (void)setAttributedText:(NSAttributedString *)attributedText { [super setAttributedText:attributedText]; self.placeholderLabel.hidden = self.hasText; } @end
[self.placeholderLabel sizeToFit];根据占位文字确定label的大小,self.placeholderLabel.hidden = self.hasText;根据textView文本框是否输入(有)文字确定占位文字是否显示。