问题是当您点击键盘上的回车键时,文本字段会在向您发送消息之前将选定范围(光标位置)设置为其文本的末尾textFieldShouldReturn:
。
您需要跟踪光标位置,以便将其恢复到之前的位置。假设您引用了属性中的文本字段:
@interface ViewController () <UITextFieldDelegate>
@property (strong, nonatomic) IBOutlet UITextField *textField;
@end
您需要一个实例变量来保存先前选择的文本范围(从点击返回键之前):
@implementation ViewController {
UITextRange *priorSelectedTextRange_;
}
然后您可以编写一个方法,将选定的文本范围保存到实例变量中:
- (void)saveTextFieldSelectedTextRange {
priorSelectedTextRange_ = self.textField.selectedTextRange;
}
在 中textFieldShouldReturn:
,在插入 pilcrow 之前,您可以将选定的文本范围更改回其先前值:
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
textField.selectedTextRange = priorSelectedTextRange_;
[textField insertText:@"¶"];
return NO;
}
但是我们怎样才能让系统saveTextFieldSelectedTextRange
在我们需要的时候发送消息呢?
该UITextFieldDelegate
协议没有有关更改所选范围的消息。
UITextField
不会发布任何有关所选范围更改的通知。
该UITextInputDelegate
协议确实有selectionWillChange:
和selectionDidChange:
消息,但是当文本字段开始编辑时,系统会将文本字段设置inputDelegate
为自己的UIKeyboardImpl
对象,因此我们不能使用inputDelegate
.
对文本字段属性的键值观察selectedTextRange
是不可靠的。在我对 iOS 6.0 模拟器的测试中,当我通过点击文本字段将光标从文本的中间移动到末尾时,我没有收到 KVO 消息。
我能想到的可靠跟踪对文本字段选定范围的更改的唯一方法是在运行循环中添加一个观察者。每次通过事件循环时,观察者都会在事件处理之前运行,因此它可以在当前选定的范围发生变化之前获取它。
所以我们实际上需要另一个实例变量来保存对我们的运行循环观察者的引用:
@implementation ViewController {
UITextRange *priorSelectedTextRange_;
CFRunLoopObserverRef runLoopObserver_;
}
我们在以下位置创建观察者viewDidLoad
:
- (void)viewDidLoad {
[super viewDidLoad];
[self createRunLoopObserver];
}
viewDidUnload
我们在和 中销毁它dealloc
:
- (void)viewDidUnload {
[super viewDidUnload];
[self destroyRunLoopObserver];
}
- (void)dealloc {
[self destroyRunLoopObserver];
}
要创建观察者,我们需要一个普通的旧 C 函数来调用它。这是那个功能:
static void runLoopObserverCallback(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
__unsafe_unretained ViewController *self = (__bridge ViewController *)info;
[self saveTextFieldSelectedTextRange];
}
现在我们可以实际创建观察者并将其注册到主运行循环中:
- (void)createRunLoopObserver {
runLoopObserver_ = CFRunLoopObserverCreate(NULL, kCFRunLoopAfterWaiting, YES, 0, &runLoopObserverCallback, &(CFRunLoopObserverContext){
.version = 0,
.info = (__bridge void *)self,
.retain = CFRetain,
.release = CFRelease,
.copyDescription = CFCopyDescription
});
CFRunLoopAddObserver(CFRunLoopGetMain(), runLoopObserver_, kCFRunLoopCommonModes);
}
下面是我们实际注销观察者并销毁它的方式:
- (void)destroyRunLoopObserver {
if (runLoopObserver_) {
CFRunLoopRemoveObserver(CFRunLoopGetMain(), runLoopObserver_, kCFRunLoopCommonModes);
CFRelease(runLoopObserver_);
runLoopObserver_ = NULL;
}
}
这种方法适用于我在 iOS 6.0 模拟器上的测试。