歡迎"@WilliamAlex大叔"一起討論iOS技術(shù)
項(xiàng)目需求: 使用textField時(shí),占位文字默認(rèn)是黑色的,我們的需求是當(dāng)開(kāi)始編輯時(shí),讓占位文字和光標(biāo)變成紅色(或其他顏色)
思路: textField和button類(lèi)似,內(nèi)部都擁有子控件,在OC機(jī)制中,所有控件內(nèi)部都是以懶加載的形式添加的.我們可以拿到textField中的子控件label,通過(guò)監(jiān)聽(tīng)textField的狀態(tài),設(shè)置內(nèi)部子控件label的樣式.
方法: 有四中方法:
1, 代理. 2, 通知. 3, target. 4, 是否是第一相應(yīng)者.
方法一: 使用target方法監(jiān)聽(tīng)富文本字符串的改變.
// 1, 首先在xib中描述登錄界面
// 2, 新建一個(gè)繼承于UITextField的類(lèi),將xib中的所有TextFiled都綁定這個(gè)類(lèi).
// 方法1: 通過(guò)富文本字符串來(lái)設(shè)置樣式
- (void)awakeFromNib
{
// 1, 設(shè)置光標(biāo)的顏色
self.tintColor = [UIColor whiteColor];
// 2, 監(jiān)聽(tīng)textField的開(kāi)始編輯狀態(tài)
[self addTarget:self action:@selector(textFieldBeginEditing) forControlEvents:UIControlEventEditingDidBegin];
// 3, 監(jiān)聽(tīng)textField的結(jié)束編輯
[self addTarget:self action:@selector(textFieldEndEditing) forControlEvents:UIControlEventEditingDidEnd];
// 4, 描述占位文字屬性
NSMutableDictionary *attr = [NSMutableDictionary dictionary];
attr[NSForegroundColorAttributeName] = [UIColor darkGrayColor];
// 5, 富文本屬性
NSAttributedString *attribute = [[NSAttributedString alloc] initWithString:self.placeholder attributes:attr];
self.attributedPlaceholder = attribute;
}
- 注意: 當(dāng)不知道設(shè)置某個(gè)控件的什么屬性時(shí),先去頭文件中找,有沒(méi)有和占位文字相關(guān)的屬性或者方法.如果實(shí)在找不到可以使用runtime,遍歷內(nèi)部的子控件,拿到它對(duì)應(yīng)的控件設(shè)置屬性值即可.
#pragma mark - 事件監(jiān)聽(tīng)方法
// 監(jiān)聽(tīng)開(kāi)始編輯
- (void)textFieldBeginEditing {
// 1, 描述占位文字屬性
NSMutableDictionary *attr = [NSMutableDictionary dictionary];
attr[NSForegroundColorAttributeName] = [UIColor whiteColor];
// 2, 富文本屬性
NSAttributedString *attribute = [[NSAttributedString alloc] initWithString:self.placeholder attributes:attr];
self.attributedPlaceholder = attribute;
}
// 監(jiān)聽(tīng)結(jié)束編輯
- (void)textFieldEndEditing {
// 4, 描述占位文字屬性
NSMutableDictionary *attr = [NSMutableDictionary dictionary];
attr[NSForegroundColorAttributeName] = [UIColor darkGrayColor];
// 5, 富文本屬性
NSAttributedString *attribute = [[NSAttributedString alloc] initWithString:self.placeholder attributes:attr];
self.attributedPlaceholder = attribute;
}
方法二 :
- 不使用富文本字符串的形式修改占位文字的字體顏色.因?yàn)橹貜?fù)代碼太多.前面講過(guò)最好是直接給對(duì)象控件設(shè)置顏色.TextFiled和Button一樣,內(nèi)部之所以能夠顯示文字,是因?yàn)槠鋬?nèi)部包含有l(wèi)abel.所以只要我們,拿到這個(gè)占位用的label,就可以直接設(shè)置顏色.
- 主要思路: 方法二的主要思路是利用KVC思想,拿到TextFiled內(nèi)部中的子控件,在使用KVC之前,用runtime變出TextFiled中所有子控件,找到placeholderLabel即可.
- (void)awakeFromNib
{
UITextField *textFiled;
// 1.設(shè)置光標(biāo)
self.tintColor = [UIColor whiteColor];
// 監(jiān)聽(tīng)開(kāi)始編輯
[self addTarget:self action:@selector(textBegin) forControlEvents:UIControlEventEditingDidBegin];
// 監(jiān)聽(tīng)結(jié)束編輯
[self addTarget:self action:@selector(textEnd) forControlEvents:UIControlEventEditingDidEnd];
// 根據(jù)屬性名獲取這個(gè)屬性的值
UILabel *placeholderLabel = [self valueForKeyPath:@"placeholderLabel"];
placeholderLabel.textColor = [UIColor lightGrayColor];
}
// 文本框開(kāi)始編輯
- (void)textBegin
{
// 修改占位文字樣式
// 根據(jù)屬性名獲取這個(gè)屬性的值
UILabel *placeholderLabel = [self valueForKeyPath:@"placeholderLabel"];
placeholderLabel.textColor = [UIColor whiteColor];
}
// 文本框結(jié)束編輯
- (void)textEnd
{
// 根據(jù)屬性名獲取這個(gè)屬性的值
UILabel *placeholderLabel = [self valueForKeyPath:@"placeholderLabel"];
placeholderLabel.textColor = [UIColor lightGrayColor];
}
- 注意 : 這樣設(shè)置相對(duì)上一中方法來(lái)說(shuō)就相對(duì)比較簡(jiǎn)潔一點(diǎn).但是,我們最好是將設(shè)置封裝到一個(gè)分類(lèi)中,提高代碼的復(fù)用.
封裝代碼
- (void)awakeFromNib
{
// 1.設(shè)置光標(biāo)
self.tintColor = [UIColor whiteColor];
// 監(jiān)聽(tīng)開(kāi)始編輯
[self addTarget:self action:@selector(textBegin) forControlEvents:UIControlEventEditingDidBegin];
// 監(jiān)聽(tīng)結(jié)束編輯
[self addTarget:self action:@selector(textEnd) forControlEvents:UIControlEventEditingDidEnd];
// 根據(jù)屬性名獲取這個(gè)屬性的值
self.placeholderColor = [UIColor lightGrayColor];
}
// 文本框開(kāi)始編輯
- (void)textBegin
{
// 修改占位文字樣式
self.placeholderColor = [UIColor whiteColor];
}
// 文本框結(jié)束編輯
- (void)textEnd
{
self.placeholderColor = [UIColor lightGrayColor];
}
- 定義一個(gè)分類(lèi),將設(shè)置占位文字顏色的具體實(shí)現(xiàn)封裝到分類(lèi)中.
.h文件
// 需要將屬性定義在.h文件中,方便外界調(diào)用
// 設(shè)置占位文字顏色
@property UIColor *placeholderColor;
#import "UITextField+Placeholder.h"
#import <objc/message.h>
// runtime:主要目的操作系統(tǒng)的類(lèi)
// OC系統(tǒng)自帶控件中,所有的子控件都是懶加載
@implementation UITextField (Placeholder)
+ (void)load
{
Method setPlaceholderMethod = class_getInstanceMethod(self, @selector(setPlaceholder:));
Method wg_setPlaceholderMethod = class_getInstanceMethod(self, @selector(wg_setPlaceholder:));
// 交互方法
method_exchangeImplementations(setPlaceholderMethod, wg_setPlaceholderMethod);
}
// 設(shè)置占位文字顏色
- (void)setPlaceholderColor:(UIColor *)placeholderColor
{
// 1.保存占位文字顏色到系統(tǒng)的類(lèi),關(guān)聯(lián)
// object:保存到哪個(gè)對(duì)象中
// key:屬性名
// value:屬性值
// policy:策略
objc_setAssociatedObject(self, @"placeholderColor", placeholderColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
UILabel *placeholderLabel = [self valueForKeyPath:@"placeholderLabel"];
placeholderLabel.textColor = placeholderColor;
}
- (UIColor *)placeholderColor
{
return objc_getAssociatedObject(self, @"placeholderColor");
}
- (void)wg_setPlaceholder:(NSString *)placeholder
{
// 設(shè)置占位文字
[self wg_setPlaceholder:placeholder];
// 設(shè)置占位文字顏色
self.placeholderColor = self.placeholderColor;
}
- 知識(shí)拓展: 在使用代理時(shí),最好不要讓自己成為自己的代理,雖然不會(huì)報(bào)錯(cuò),但是不符合代理的原理.所以,建議不要使用這種方法,上面所述的方法.主要是想知道有這么個(gè)方法和過(guò)程.在實(shí)際開(kāi)發(fā)中我會(huì)運(yùn)用的方法如下:
使用Target監(jiān)聽(tīng)TextField內(nèi)部控件的私有屬性.
- 首先是使用運(yùn)行時(shí)機(jī)制獲取某個(gè)控件內(nèi)部的私有屬性
// 1, 獲取私有屬性
// 運(yùn)行時(shí)獲取類(lèi)中的私有屬性
unsigned int count;
// 運(yùn)行時(shí)打印出文本框中的所有成員屬性
Ivar *ivarList = class_copyIvarList([UITextField class], &count);
for (int i =0; i< count; i++) {
Ivar ivar = ivarList[i];
NSLog(@"%s",ivar_getName(ivar));
}
free(ivarList);
- 其次將從遍歷出來(lái)的屬性中找到想要修改的屬性
// 2, 找到對(duì)應(yīng)的私有屬性,將它設(shè)置為一個(gè)宏
static NSString * const WGPlaceholderLabel = @"placeholderLabel.textColor";
- 然后監(jiān)聽(tīng)私有屬性
// 設(shè)置占位文字原有顏色
[self setValue:[UIColor grayColor] forKeyPath:WGPlaceholderLabel];
// 監(jiān)聽(tīng)文本框開(kāi)始編輯
[self addTarget:self action:@selector(editingBegin) forControlEvents:UIControlEventEditingDidBegin];
// 監(jiān)聽(tīng)文本框結(jié)束編輯
[self addTarget:self action:@selector(editingEnd) forControlEvents:UIControlEventEditingDidEnd];
- 最后實(shí)現(xiàn)監(jiān)聽(tīng)方法
#pragma mark - 監(jiān)聽(tīng)文本框的編輯狀態(tài)
- (void)editingBegin{
[self setValue:[UIColor whiteColor] forKeyPath:WGPlaceholderLabel];
}
- (void)editingEnd {
[self setValue:[UIColor grayColor] forKeyPath:WGPlaceholderLabel];
}
- 總結(jié):
- 1, 文本框和按鈕一樣,都可以編輯文字,所以?xún)?nèi)部是有l(wèi)abel的,所以需要拿到文本框中的label(可以在"小面包中檢測(cè)"),當(dāng)輸入文字后,有個(gè)label就會(huì)消失,那個(gè)就是占位label,所以需要拿到系統(tǒng)內(nèi)部的私有屬性,但是不能直接拿到私有的屬性和方法,所以需要用到KVC去取值和賦值.通過(guò)"運(yùn)行時(shí)"拿到屬性
- 2, 然后通過(guò)KVC取值
容易出錯(cuò)點(diǎn): 不要用setValu:forKey,程序會(huì)崩掉,要用forKeyPath:表示不管你在文件的那一層都能去拿到
代理(不推薦使用)
- 這里使用代理需要注意一點(diǎn):它是自己成為了自己的代理,雖然可以實(shí)現(xiàn)項(xiàng)目需求,但是,這和"代理"的本質(zhì)有沖突.
- 代理本質(zhì): 自己不能做的事,讓自己的代理去做
/*
總結(jié): 本章是用代理的方式監(jiān)聽(tīng)文本框的編輯狀態(tài),然后通過(guò)拿到UITextField中的私有的_placeholderLabel屬性,給私有屬性賦值即可,通過(guò)運(yùn)行時(shí)獲取查看其內(nèi)部的所有私有屬性,然后通過(guò)KVC賦值;
*/
#import "WGLoginRegisterTextFiled.h"
#import <objc/message.h>
static NSString * const WGPlaceholderLabel = @"_placeholderLabel.textColor";
@interface WGLoginRegisterTextFiled () <UITextFieldDelegate>
@end
@implementation WGLoginRegisterTextFiled
- (void)awakeFromNib {
// 設(shè)置光標(biāo)顏色
self.tintColor = [UIColor cyanColor];
// 通過(guò)運(yùn)行時(shí)獲取TextFiled的內(nèi)部私有屬性
unsigned int count ;
Ivar *ivarList = class_copyIvarList([UITextField class], &count);
// 遍歷UITextField的所有屬性,ivarList(指針)實(shí)質(zhì)是一個(gè)數(shù)組
for (int i = 0; i < count; i++) {
// 獲取每一個(gè)屬性
Ivar ivar = ivarList[i];
NSLog(@"%s",ivar_getName(ivar)); // "%s"原因是運(yùn)行時(shí)是一個(gè)C語(yǔ)言的語(yǔ)法
}
// 銷(xiāo)毀運(yùn)行時(shí)創(chuàng)建的ivarList
free(ivarList);
// 通過(guò)KVC將屬性取出來(lái)并賦值
[self setValue:[UIColor grayColor] forKeyPath:WGPlaceholderLabel];
// 設(shè)置代理
self.delegate = self; // 自己成為自己的代理,在實(shí)際開(kāi)發(fā)中不這樣寫(xiě),這和代理的原理意義上違背了
}
#pragma mark - 設(shè)置代理的方法
// 開(kāi)始編輯
- (void)textFieldDidBeginEditing:(UITextField *)textField {
// KVC
[self setValue:[UIColor whiteColor] forKeyPath:WGPlaceholderLabel];
}
// 結(jié)束編輯
- (void)textFieldDidEndEditing:(UITextField *)textField {
[self setValue:[UIColor grayColor] forKeyPath:WGPlaceholderLabel];
}
根據(jù)某些控件特有的方法或者屬性來(lái)設(shè)置
- TextFiled有一個(gè)特別的屬性方法:即是否成為第一響應(yīng)者和不當(dāng)?shù)谝豁憫?yīng)者.根據(jù)這樣的特性做一些操作.
/*
分析:UITextField有一個(gè)特有的屬性,就是響應(yīng)者屬性,通過(guò)重寫(xiě)成為第一響應(yīng)者和辭去第一響應(yīng)者來(lái)給占位文字設(shè)置顏色
*/
#import "WGLoginRegisterTextFiled.h"
#import <objc/message.h>
static NSString * const WGPlaceholderLabel = @"_placeholderLabel.textColor";
@interface WGLoginRegisterTextFiled () <UITextFieldDelegate>
@end
@implementation WGLoginRegisterTextFiled
- (void)awakeFromNib {
// 設(shè)置光標(biāo)顏色
self.tintColor = [UIColor cyanColor];
// 通過(guò)KVC將屬性取出來(lái)并賦值
[self setValue:[UIColor grayColor] forKeyPath:WGPlaceholderLabel];
// 調(diào)用時(shí)刻: 當(dāng)成為第一響應(yīng)者 /獲取焦點(diǎn) /彈出鍵盤(pán)
[self becomeFirstResponder];
// 調(diào)用時(shí)刻: 當(dāng)失去第一響應(yīng)者 /獲取焦點(diǎn) /彈出鍵盤(pán)
[self resignFirstResponder];
}
#pragma mark - 設(shè)置代理的方法
// 調(diào)用時(shí)刻: 當(dāng)成為第一響應(yīng)者 /獲取焦點(diǎn) /彈出鍵盤(pán)
- (BOOL)becomeFirstResponder {
[self setValue:[UIColor whiteColor] forKeyPath:WGPlaceholderLabel];
return [super becomeFirstResponder];
}
// 調(diào)用時(shí)刻: 當(dāng)失去第一響應(yīng)者 /獲取焦點(diǎn) /彈出鍵盤(pán)
- (BOOL)resignFirstResponder {
[self setValue:[UIColor grayColor] forKeyPath:WGPlaceholderLabel];
return [super resignFirstResponder];
}
@end
-
總結(jié):其實(shí)還有通知方法的,和代理類(lèi)似,所以就不寫(xiě)了.本章重點(diǎn).
1, 學(xué)會(huì)編程思想,不要局限于某一種方式開(kāi)發(fā)
2, 學(xué)會(huì)利用某些特有的方法或者屬性來(lái)解決問(wèn)題
3, 如果有時(shí)間可以深入學(xué)習(xí)運(yùn)行時(shí)機(jī)制(特別有意思)
知識(shí)拓展: 在使用代理時(shí),最好不要讓自己成為自己的代理,雖然不會(huì)報(bào)錯(cuò),但是不符合代理的原理.所以,建議不要使用這種方法
注意: 本章使用的target監(jiān)聽(tīng)textFiled的編輯狀態(tài),但是這是不符合代理的原理,在實(shí)際開(kāi)發(fā)中"盡量不要自己成為自己的代理".我的建議是使用textFiled的特性,即"第一響應(yīng)者"來(lái)監(jiān)聽(tīng)textFiled的編輯狀態(tài).