昨晚睡觉前看到群里有人提到说要怎么把 UITextField
的光标换成图片,然后好像没人提方案,心想光标十有八九也是 UIView
的子类,就起床实现了下。主要是先看看它的本质、有什么样的行为,再根据已有的信息去实现需求。这称不上多精湛的技巧,只是看别人提这类问题问得不少,所以才写出来,万一有人看呢?
本质
第一步是挖掘它的本质,猜测会不会是各种私有类的实例,有没有什么方法、属性可以直接拿来用。
直接看 View Hierarchy 是最方便快捷的了,选中那个蓝色光标可以发现它只是个 UIView
实例,那么就可以考虑修改它的 layer.contents
,在上面绘制图片。
接下来是写一个方法去获取它:
-(UIView *)findCursor:(UITextField *)tf {
for (UIView *sv in tf.subviews) {
if ([sv isMemberOfClass:NSClassFromString(@"UIFieldEditor")]) {
for (UIView *v in sv.subviews) {
if ([v isMemberOfClass:NSClassFromString(@"_UIFieldEditorContentView")]) {
for (UIView *vv in v.subviews) {
if ([vv isMemberOfClass:NSClassFromString(@"UITextSelectionView")]) {
return vv.subviews.lastObject;
}
}
}
}
}
}
return nil;
}
行为
除了知道它是什么以外,还需要知道它会有什么样的行为、表现:
- 在
-textFieldDidBeginEditing:
中尝试获得光标,返回的结果是 nil。由此可见,delegate 回调时,输入框的光标还没被添加到UITextSelectionView
的实例上。经测试,需要延时 0.15 秒左右才能获取得到光标。 - 获得光标并设置了它的
frame
和layer.contents
属性后,光标确实发生了变化,但随着编辑的进行,光标的frame
又恢复为默认值。这是因为输入框成为 First Responder 或编辑时都会触发-layoutSubviews
方法,那么-layoutSubviews
才是比较好的地方去自定义光标的几何属性; - 每次输入框成为 First Responder 时,光标都是新的
UIView
实例; - 在另一个 window 跳出
UICalloutBar
实例的时候,光标的frame
会变为默认值;
- 长按输入框,让光标移动或进行文字选择时,
frame
会变为默认值;
- 即使在
-layoutSubviews
内改变frame
的值,输入中文的时候frame
仍然会不时地会变为默认值,观察不出规律;
- 在文本中间开始编辑,光标的
size
太大会遮挡后面的文字。
综合 1,2,3 点,写一个 UITextField
的子类在 -layoutSubviews
中获取并改变光标的内容,
但结合 4,5,6,7 这四点,改变光标的 frame
不是个好主意,除非你就是想要那种变幻莫测的效果。
@interface YourTextField ()
@property (weak, nonatomic) UIView *cursor;
@end
@implementation YourTextField
-(void)layoutSubviews {
[super layoutSubviews];
if (_cursor == nil) {
_cursor = [self findCursor];
_cursor.layer.contents = (__bridge id _Nullable)([UIImage imageNamed:@"img"].CGImage);
}
}
-(UIView *)findCursor {
for (UIView *sv in self.subviews) {
if ([sv isMemberOfClass:NSClassFromString(@"UIFieldEditor")]) {
for (UIView *v in sv.subviews) {
if ([v isMemberOfClass:NSClassFromString(@"_UIFieldEditorContentView")]) {
for (UIView *vv in v.subviews) {
if ([vv isMemberOfClass:NSClassFromString(@"UITextSelectionView")]) {
return vv.subviews.lastObject;
}
}
}
}
}
}
return nil;
}
@end
其中,cursor
是用 weak
修饰的属性,之所以这么干是因为两个原因:
- 引用实例,避免在编辑时不断地去获取光标;
- 在结束编辑的时候,光标不被强引用,
cursor
被置为 nil 以便下次获取新实例。
其它
更博客不难,坚持抽时间更博客就没那么轻松了。
下星期又是忙碌的一周。🌝