欢迎"@WilliamAlex大叔"一起讨论iOS技术
项目需求: 使用textField时,占位文字默认是黑色的,我们的需求是当开始编辑时,让占位文字和光标变成红色(或其他颜色)
思路: textField和button类似,内部都拥有子控件,在OC机制中,所有控件内部都是以懒加载的形式添加的.我们可以拿到textField中的子控件label,通过监听textField的状态,设置内部子控件label的样式.
方法: 有四中方法:
1, 代理. 2, 通知. 3, target. 4, 是否是第一相应者.
方法一: 使用target方法监听富文本字符串的改变.
// 1, 首先在xib中描述登录界面
// 2, 新建一个继承于UITextField的类,将xib中的所有TextFiled都绑定这个类.
// 方法1: 通过富文本字符串来设置样式
- (void)awakeFromNib
{
// 1, 设置光标的颜色
self.tintColor = [UIColor whiteColor];
// 2, 监听textField的开始编辑状态
[self addTarget:self action:@selector(textFieldBeginEditing) forControlEvents:UIControlEventEditingDidBegin];
// 3, 监听textField的结束编辑
[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;
}
- 注意: 当不知道设置某个控件的什么属性时,先去头文件中找,有没有和占位文字相关的属性或者方法.如果实在找不到可以使用runtime,遍历内部的子控件,拿到它对应的控件设置属性值即可.
#pragma mark - 事件监听方法
// 监听开始编辑
- (void)textFieldBeginEditing {
// 1, 描述占位文字属性
NSMutableDictionary *attr = [NSMutableDictionary dictionary];
attr[NSForegroundColorAttributeName] = [UIColor whiteColor];
// 2, 富文本属性
NSAttributedString *attribute = [[NSAttributedString alloc] initWithString:self.placeholder attributes:attr];
self.attributedPlaceholder = attribute;
}
// 监听结束编辑
- (void)textFieldEndEditing {
// 4, 描述占位文字属性
NSMutableDictionary *attr = [NSMutableDictionary dictionary];
attr[NSForegroundColorAttributeName] = [UIColor darkGrayColor];
// 5, 富文本属性
NSAttributedString *attribute = [[NSAttributedString alloc] initWithString:self.placeholder attributes:attr];
self.attributedPlaceholder = attribute;
}
方法二 :
- 不使用富文本字符串的形式修改占位文字的字体颜色.因为重复代码太多.前面讲过最好是直接给对象控件设置颜色.TextFiled和Button一样,内部之所以能够显示文字,是因为其内部包含有label.所以只要我们,拿到这个占位用的label,就可以直接设置颜色.
- 主要思路: 方法二的主要思路是利用KVC思想,拿到TextFiled内部中的子控件,在使用KVC之前,用runtime变出TextFiled中所有子控件,找到placeholderLabel即可.
- (void)awakeFromNib
{
UITextField *textFiled;
// 1.设置光标
self.tintColor = [UIColor whiteColor];
// 监听开始编辑
[self addTarget:self action:@selector(textBegin) forControlEvents:UIControlEventEditingDidBegin];
// 监听结束编辑
[self addTarget:self action:@selector(textEnd) forControlEvents:UIControlEventEditingDidEnd];
// 根据属性名获取这个属性的值
UILabel *placeholderLabel = [self valueForKeyPath:@"placeholderLabel"];
placeholderLabel.textColor = [UIColor lightGrayColor];
}
// 文本框开始编辑
- (void)textBegin
{
// 修改占位文字样式
// 根据属性名获取这个属性的值
UILabel *placeholderLabel = [self valueForKeyPath:@"placeholderLabel"];
placeholderLabel.textColor = [UIColor whiteColor];
}
// 文本框结束编辑
- (void)textEnd
{
// 根据属性名获取这个属性的值
UILabel *placeholderLabel = [self valueForKeyPath:@"placeholderLabel"];
placeholderLabel.textColor = [UIColor lightGrayColor];
}
- 注意 : 这样设置相对上一中方法来说就相对比较简洁一点.但是,我们最好是将设置封装到一个分类中,提高代码的复用.
封装代码
- (void)awakeFromNib
{
// 1.设置光标
self.tintColor = [UIColor whiteColor];
// 监听开始编辑
[self addTarget:self action:@selector(textBegin) forControlEvents:UIControlEventEditingDidBegin];
// 监听结束编辑
[self addTarget:self action:@selector(textEnd) forControlEvents:UIControlEventEditingDidEnd];
// 根据属性名获取这个属性的值
self.placeholderColor = [UIColor lightGrayColor];
}
// 文本框开始编辑
- (void)textBegin
{
// 修改占位文字样式
self.placeholderColor = [UIColor whiteColor];
}
// 文本框结束编辑
- (void)textEnd
{
self.placeholderColor = [UIColor lightGrayColor];
}
- 定义一个分类,将设置占位文字颜色的具体实现封装到分类中.
.h文件
// 需要将属性定义在.h文件中,方便外界调用
// 设置占位文字颜色
@property UIColor *placeholderColor;
#import "UITextField+Placeholder.h"
#import <objc/message.h>
// runtime:主要目的操作系统的类
// OC系统自带控件中,所有的子控件都是懒加载
@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);
}
// 设置占位文字颜色
- (void)setPlaceholderColor:(UIColor *)placeholderColor
{
// 1.保存占位文字颜色到系统的类,关联
// object:保存到哪个对象中
// 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
{
// 设置占位文字
[self wg_setPlaceholder:placeholder];
// 设置占位文字颜色
self.placeholderColor = self.placeholderColor;
}
- 知识拓展: 在使用代理时,最好不要让自己成为自己的代理,虽然不会报错,但是不符合代理的原理.所以,建议不要使用这种方法,上面所述的方法.主要是想知道有这么个方法和过程.在实际开发中我会运用的方法如下:
使用Target监听TextField内部控件的私有属性.
- 首先是使用运行时机制获取某个控件内部的私有属性
// 1, 获取私有属性
// 运行时获取类中的私有属性
unsigned int count;
// 运行时打印出文本框中的所有成员属性
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);
- 其次将从遍历出来的属性中找到想要修改的属性
// 2, 找到对应的私有属性,将它设置为一个宏
static NSString * const WGPlaceholderLabel = @"placeholderLabel.textColor";
- 然后监听私有属性
// 设置占位文字原有颜色
[self setValue:[UIColor grayColor] forKeyPath:WGPlaceholderLabel];
// 监听文本框开始编辑
[self addTarget:self action:@selector(editingBegin) forControlEvents:UIControlEventEditingDidBegin];
// 监听文本框结束编辑
[self addTarget:self action:@selector(editingEnd) forControlEvents:UIControlEventEditingDidEnd];
- 最后实现监听方法
#pragma mark - 监听文本框的编辑状态
- (void)editingBegin{
[self setValue:[UIColor whiteColor] forKeyPath:WGPlaceholderLabel];
}
- (void)editingEnd {
[self setValue:[UIColor grayColor] forKeyPath:WGPlaceholderLabel];
}
- 总结:
- 1, 文本框和按钮一样,都可以编辑文字,所以内部是有label的,所以需要拿到文本框中的label(可以在"小面包中检测"),当输入文字后,有个label就会消失,那个就是占位label,所以需要拿到系统内部的私有属性,但是不能直接拿到私有的属性和方法,所以需要用到KVC去取值和赋值.通过"运行时"拿到属性
- 2, 然后通过KVC取值
容易出错点: 不要用setValu:forKey,程序会崩掉,要用forKeyPath:表示不管你在文件的那一层都能去拿到
代理(不推荐使用)
- 这里使用代理需要注意一点:它是自己成为了自己的代理,虽然可以实现项目需求,但是,这和"代理"的本质有冲突.
- 代理本质: 自己不能做的事,让自己的代理去做
/*
总结: 本章是用代理的方式监听文本框的编辑状态,然后通过拿到UITextField中的私有的_placeholderLabel属性,给私有属性赋值即可,通过运行时获取查看其内部的所有私有属性,然后通过KVC赋值;
*/
#import "WGLoginRegisterTextFiled.h"
#import <objc/message.h>
static NSString * const WGPlaceholderLabel = @"_placeholderLabel.textColor";
@interface WGLoginRegisterTextFiled () <UITextFieldDelegate>
@end
@implementation WGLoginRegisterTextFiled
- (void)awakeFromNib {
// 设置光标颜色
self.tintColor = [UIColor cyanColor];
// 通过运行时获取TextFiled的内部私有属性
unsigned int count ;
Ivar *ivarList = class_copyIvarList([UITextField class], &count);
// 遍历UITextField的所有属性,ivarList(指针)实质是一个数组
for (int i = 0; i < count; i++) {
// 获取每一个属性
Ivar ivar = ivarList[i];
NSLog(@"%s",ivar_getName(ivar)); // "%s"原因是运行时是一个C语言的语法
}
// 销毁运行时创建的ivarList
free(ivarList);
// 通过KVC将属性取出来并赋值
[self setValue:[UIColor grayColor] forKeyPath:WGPlaceholderLabel];
// 设置代理
self.delegate = self; // 自己成为自己的代理,在实际开发中不这样写,这和代理的原理意义上违背了
}
#pragma mark - 设置代理的方法
// 开始编辑
- (void)textFieldDidBeginEditing:(UITextField *)textField {
// KVC
[self setValue:[UIColor whiteColor] forKeyPath:WGPlaceholderLabel];
}
// 结束编辑
- (void)textFieldDidEndEditing:(UITextField *)textField {
[self setValue:[UIColor grayColor] forKeyPath:WGPlaceholderLabel];
}
根据某些控件特有的方法或者属性来设置
- TextFiled有一个特别的属性方法:即是否成为第一响应者和不当第一响应者.根据这样的特性做一些操作.
/*
分析:UITextField有一个特有的属性,就是响应者属性,通过重写成为第一响应者和辞去第一响应者来给占位文字设置颜色
*/
#import "WGLoginRegisterTextFiled.h"
#import <objc/message.h>
static NSString * const WGPlaceholderLabel = @"_placeholderLabel.textColor";
@interface WGLoginRegisterTextFiled () <UITextFieldDelegate>
@end
@implementation WGLoginRegisterTextFiled
- (void)awakeFromNib {
// 设置光标颜色
self.tintColor = [UIColor cyanColor];
// 通过KVC将属性取出来并赋值
[self setValue:[UIColor grayColor] forKeyPath:WGPlaceholderLabel];
// 调用时刻: 当成为第一响应者 /获取焦点 /弹出键盘
[self becomeFirstResponder];
// 调用时刻: 当失去第一响应者 /获取焦点 /弹出键盘
[self resignFirstResponder];
}
#pragma mark - 设置代理的方法
// 调用时刻: 当成为第一响应者 /获取焦点 /弹出键盘
- (BOOL)becomeFirstResponder {
[self setValue:[UIColor whiteColor] forKeyPath:WGPlaceholderLabel];
return [super becomeFirstResponder];
}
// 调用时刻: 当失去第一响应者 /获取焦点 /弹出键盘
- (BOOL)resignFirstResponder {
[self setValue:[UIColor grayColor] forKeyPath:WGPlaceholderLabel];
return [super resignFirstResponder];
}
@end
-
总结:其实还有通知方法的,和代理类似,所以就不写了.本章重点.
1, 学会编程思想,不要局限于某一种方式开发
2, 学会利用某些特有的方法或者属性来解决问题
3, 如果有时间可以深入学习运行时机制(特别有意思)
知识拓展: 在使用代理时,最好不要让自己成为自己的代理,虽然不会报错,但是不符合代理的原理.所以,建议不要使用这种方法
注意: 本章使用的target监听textFiled的编辑状态,但是这是不符合代理的原理,在实际开发中"尽量不要自己成为自己的代理".我的建议是使用textFiled的特性,即"第一响应者"来监听textFiled的编辑状态.