Objective c 如何在当前光标位置将文本插入UITextField?

Objective c 如何在当前光标位置将文本插入UITextField?,objective-c,ios,cocoa-touch,Objective C,Ios,Cocoa Touch,我正在尝试使用UITextField的“return”键插入自定义字符。以下是我的UITextFieldDelegate方法的外观: - (BOOL)textFieldShouldReturn:(UITextField *)textField { [textField insertText:@"¶"]; return NO; } 不幸的是,这只在某些时候起作用: “一二”-->移动光标-->“一二”-->返回-->“一二”(确定) “onetwo |”-->return-->

我正在尝试使用UITextField的“return”键插入自定义字符。以下是我的UITextFieldDelegate方法的外观:

- (BOOL)textFieldShouldReturn:(UITextField *)textField {
    [textField insertText:@"¶"];
    return NO;
}
不幸的是,这只在某些时候起作用:

  • “一二”-->移动光标-->“一二”-->返回-->“一二”(确定
  • “onetwo |”-->return-->“onetwo |”(OK
  • “onetwo |”-->move cursor-->“one | two”-->return-->“onetwo |”(失败
在最后一种情况下,我会期望“一|二”

如何确保插入的文本始终插入到光标位置


谢谢。

这里发生的事情是,您没有跟踪插入点,也称为选择范围

要做到这一点,您需要深入了解UITextField的功能


,您可以获取,它告诉您插入符号(光标、插入点)的位置,以及您应该在那里插入特殊字符。如果您将对象设置为符合“
UITEPUT
”协议的委托,则此操作应该有效。

问题在于,当您点击键盘上的返回键时,文本字段会设置所选范围(光标位置)在其文本的末尾,在之前,它会向您发送
文本字段shouldReturn:
消息

您需要跟踪光标位置,以便将其恢复到先前的位置。假设您对属性中的文本字段有引用:

@interface ViewController () <UITextFieldDelegate>

@property (strong, nonatomic) IBOutlet UITextField *textField;

@end
然后,您可以编写一个方法,将选定的文本范围保存到实例变量:

- (void)saveTextFieldSelectedTextRange {
    priorSelectedTextRange_ = self.textField.selectedTextRange;
}
textFieldShouldReturn:
中,插入pilcrow之前,可以将选定的文本范围更改回其先前的值:

- (BOOL)textFieldShouldReturn:(UITextField *)textField {
    textField.selectedTextRange = priorSelectedTextRange_;
    [textField insertText:@"¶"];
    return NO;
}
但是,我们如何让系统在需要时发送
savetextfieldselectedtextange
消息

  • UITextFieldDelegate
    协议没有更改所选范围的消息

  • UITextField
    不会发布所选范围更改的任何通知

  • uideputDelegate
    协议确实有
    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模拟器上的测试。

同样的问题:输入“OneTo”后,将光标移动到“one | two”后,
selectedTextRange
的开始值和结束值为6。我还发现。点击返回键时,文本字段将光标移动到末尾,在它发送
文本字段之前,应该返回:
消息,因此当您收到该消息时,查看
已选择的扩展已为时过晚。谢谢!这很有效。您建议如何在ARC项目中释放运行循环观察器?ARC不管理核心基础对象,如“代码> CfRunRobject观察器< /代码>。无论是使用MRC还是ARC,您都需要
CFRelease
中的
destroyRunLoopObserver
。好的,谢谢。我看到一个错误,因为我正在调用[super dealoc](不是因为我正在定义dealoc方法)。您不能在ARC下显式调用
[super dealoc]
。ARC总是自动调用
[super dealloc]
- (void)destroyRunLoopObserver {
    if (runLoopObserver_) {
        CFRunLoopRemoveObserver(CFRunLoopGetMain(), runLoopObserver_, kCFRunLoopCommonModes);
        CFRelease(runLoopObserver_);
        runLoopObserver_ = NULL;
    }
}