UITextView之placeHolerLabel(終結篇,不只是總結篇)

優秀是一種習慣

前言:大家項目中肯定經常用到UITextView,但是對于這個控件有一個很大的缺點,那就是沒有placeHolerLabel,搞不懂蘋果為什么不給一個這種屬性,明明應用的很廣泛

網上關于UITextView實現placeHolerLabel的方法大概有四五種,在文章后面會一一列舉 
因為網上很多了,就復制來了,本來打算跳轉一下,但是不是很喜歡,便復制了一下
原文:http://www.lxweimin.com/p/426a3fc8b067
終極篇效果.png
怕各位看官,看煩了,先上終極篇
終極篇采取了runtime方法+類別
直接上代碼
終極方法
1. 新建一個UITextView的分類(Category)
2. 聲明一個屬性placeholder
3. 實現get,set方法

代碼如下
.h
#import <UIKit/UIKit.h>

@interface UITextView (Category)
@property (nonatomic,copy)NSString * hc_placeHolerLabel;
@end

//-----------------------------------------//
.m

#import "UITextView+Category.h"

@implementation UITextView (Category)

-(void)setHc_placeHolerLabel:(NSString *)hc_placeHolerLabel{

    // _placeholderLabel
    UILabel *Label = [[UILabel alloc] init];
    Label.text = @"請輸入內容";
    Label.numberOfLines = 0;
    Label.textColor = [UIColor lightGrayColor];
    [Label sizeToFit];
    [self addSubview:Label];
    
    //很重要的一句話,如果字體大小跟textView大小不一樣會出問題
    Label.font = [UIFont systemFontOfSize:self.font.pointSize];
    
    [self setValue:Label forKey:@"_placeholderLabel"];
}

-(NSString *)hc_placeHolerLabel{

    UILabel * label = [self valueForKey:@"_placeholderLabel"];
    return label.text;
}
@end
第一種
1.把UITextView的text屬性當成“placeholder”使用。
2.在開始編輯的代理方法里清除“placeholder”。
3.在結束編輯的代理方法里根據條件設置“placeholder”。

缺點很明顯:當textView的文字跟placeHolerLabel相同時就有問題了
// 創建textView
UITextView *textView =     [[UITextViewalloc]initWithFrame:CGRectMake(20,70,SCREEN.width-40,100)];
textView.backgroundColor= [UIColor whiteColor];
textView.text = @"我是placeholder";
textView.textColor = [UIColor grayColor];
textView.delegate = self;
[self.view addSubview:textView];

#pragma mark - UITextViewDelegate
- (void)textViewDidEndEditing:(UITextView *)textView
{
    if(textView.text.length < 1){
    textView.text = @"我是placeholder";
    textView.textColor = [UIColor grayColor];
  }
 }
  - (void)textViewDidBeginEditing:(UITextView *)textView
{
   if([textView.text isEqualToString:@"我是placeholder"]){
    textView.text=@"";
      textView.textColor=[UIColor blackColor];
 }
 }
第二種
1.創建textView
2.給textView添加一個UILabel子控件,作為placeholder
3.在文本改變的代理方法里面顯示/隱藏UILabel

太麻煩了.....大哥
#import "WSViewController.h"

@interface WSViewController () <UITextViewDelegate>

@property(nonatomic, weak)UITextView *textView;

@property(nonatomic, weak)UILabel *placeHolder;

@end

@implementation WSViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.

    [self setupTextView];

}

// 添加textView
- (void)setupTextView
{
     UITextView *textView = [[UITextView alloc] initWithFrame:CGRectMake(10, 74, SCREEN_WIDTH - 2 * 10, 200)];
    textView.frame = CGRectMake(10, 74, SCREEN_WIDTH - 2 * 10, 200);

    [self.view addSubview:textView];
    self.textView = textView;

    textView.contentInset = UIEdgeInsetsMake(-64, 0, 0, 0);

    textView.delegate = self;
    [self setupPlaceHolder];


    //在彈出的鍵盤上面加一個view來放置退出鍵盤的Done按鈕
    UIToolbar * topView = [[UIToolbar alloc] initWithFrame:CGRectMake(0, 0, 320,      30)];
    [topView setBarStyle:UIBarStyleDefault];
    UIBarButtonItem * btnSpace = [[UIBarButtonItem alloc]   initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:self  action:nil];
    UIBarButtonItem * doneButton = [[UIBarButtonItem alloc] initWithTitle:@"完成" style:UIBarButtonItemStyleDone target:self action:@selector(dismissKeyBoard)];
    NSArray * buttonsArray = [NSArray arrayWithObjects:btnSpace, doneButton, nil];

    [topView setItems:buttonsArray];
    [textView setInputAccessoryView:topView];

 }

// 給textView添加一個UILabel子控件
- (void)setupPlaceHolder
{
     UILabel *placeHolder = [[UILabel alloc] initWithFrame:CGRectMake(15, -2, SCREEN_WIDTH - 2 * 15, 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;
    }
 }

//關閉鍵盤
-(void) dismissKeyBoard{
    [self.textView resignFirstResponder];
  }

@end
第三種
1.自定義UITextView
2.給UITextView添加placeholder和placeholderColor屬性
3.重寫initWithFrame方法
4.重寫drawRect:方法
5.重寫相關屬性的set方法

不麻煩嗎?
#import <UIKit/UIKit.h>

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

#import "WSTextView.h"

@implementation WSTextView
 - (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];
}
@end

 // 應用的時候需要配合UITextView的文本改變的代理方法

#import "ViewController.h"
 #import "WSTextView.h"

@interface ViewController ()<UITextViewDelegate>

// @property(nonatomic,weak) WSTextView *textView;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
}

- (void)didReceiveMemoryWarning {
  [super didReceiveMemoryWarning];
  // Dispose of any resources that can be recreated.
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    WSTextView *textView = [[WSTextView alloc] initWithFrame:CGRectMake(10, 20,     self.view.frame.size.width, 30)];
    textView.placeholder = @"ws";
    textView.delegate = self;
    [self.view addSubview:textView];
    // textView.text = @"試試會不會調用文本改變的代理方法"; // 不會調用文本改變的代理方法
    textView.attributedText = [[NSAttributedString alloc] initWithString:@"富文本"];

     // self.textView = textView;
}

#pragma mark - UITextViewDelegate
- (void)textViewDidChange:(WSTextView *)textView // 此處取巧,把代理方法參數類型直接改成自定義的WSTextView類型,為了可以使用自定義的placeholder屬性,省去了通過給控制器WSTextView類型屬性這樣一步。
{
     if (textView.hasText) { // textView.text.length
        textView.placeholder = @"";

    } else {
         textView.placeholder = @"ws";

    }
}
@end

第四種
runtime私有變量,高大上

然而也有問題,在8.1的時候是沒有這個私有變量的,所以要適配iOS8.0是有問題的


終極方法也是根據這個來的
#import "ViewController.h"
#import <objc/runtime.h>
#import <objc/message.h>

@interface ViewController ()

@end

 @implementation ViewController

- (void)viewDidLoad {
     [super viewDidLoad];


  // 通過運行時,發現UITextView有一個叫做“_placeHolderLabel”的私有變量
    unsigned int count = 0;
     Ivar *ivars = class_copyIvarList([UITextView class], &count);

    for (int i = 0; i < count; i++) {
        Ivar ivar = ivars[i];
        const char *name = ivar_getName(ivar);
        NSString *objcName = [NSString stringWithUTF8String:name];
        NSLog(@"%d : %@",i,objcName);
    }

//8.1
/*
0 : _private
1 : _textStorage
2 : _textContainer
3 : _layoutManager
4 : _containerView
5 : _inputDelegate
6 : _tokenizer
7 : _inputController
8 : _interactionAssistant
9 : _textInputTraits
10 : _autoscroll
11 : _tvFlags
12 : _linkInteractionItem
13 : _scrollTarget
14 : _scrollTargetOffset
15 : _dataDetectorTypes
16 : _preferredMaxLayoutWidth
17 : _inputAccessoryView
18 : _streamingManager
19 : _characterStreamingManager
20 : _siriAnimationStyle
21 : _siriParameters
22 : _firstBaselineOffsetFromTop
23 : _lastBaselineOffsetFromBottom
24 : _clearsOnInsertion
25 : _inputView
*/
    [self setupTextView];

 }
 - (void)setupTextView
 {
//注意這里的textView的Font跟_placeholderLabel的font一定要相同
     UITextView *textView = [[UITextView alloc] initWithFrame:CGRectMake(0, 100, [UIScreen mainScreen].bounds.size.width, 100];
     textView.font = [UIFont systemFontOfSize:14];
     [textView setBackgroundColor:[UIColor greenColor]];
     [self.view addSubview:textView];

    // _placeholderLabel
     UILabel *placeHolderLabel = [[UILabel alloc] init];
    placeHolderLabel.text = @"請輸入內容";
     placeHolderLabel.numberOfLines = 0;
     placeHolderLabel.textColor = [UIColor lightGrayColor];
     [placeHolderLabel sizeToFit];
     placeHolderLabel.font = textView.font;

     [textView addSubview:placeHolderLabel];

     [textView setValue:placeHolderLabel forKey:@"_placeholderLabel"];

 }

@end
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,763評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,238評論 3 428
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 177,823評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,604評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,339評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,713評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,712評論 3 445
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,893評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,448評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,201評論 3 357
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,397評論 1 372
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,944評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,631評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,033評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,321評論 1 293
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,128評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,347評論 2 377

推薦閱讀更多精彩內容