2

我正在尝试使用 UITextField 的“返回”键来插入自定义字符。这是我的 UITextFieldDelegate 方法的样子:

- (BOOL)textFieldShouldReturn:(UITextField *)textField {
    [textField insertText:@"¶"];
    return NO;
}

不幸的是,这只在某些时候有效:

  • “一二|” -->移动光标--> "one| two" --> return --> "one¶| two" ( OK )
  • “一个人|” -->返回--> "onetwo¶|" (好的
  • “一个人|” -->移动光标--> "one|two" -->返回--> "onetwo¶|" (失败

在最后一种情况下,我会期望“一个¶|两个”。

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

谢谢。

4

2 回答 2

3

问题是当您点击键盘上的回车键时,文本字段会在向您发送消息之前将选定范围(光标位置)设置为其文本的末尾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 模拟器上的测试。

于 2012-11-21T22:29:18.110 回答
0

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

为此,您需要更深入地了解 UITextField 可以做什么。

使用 UITextInput(可作为 UITextField 使用的协议访问),您可以获取" selectedTextRange" 属性,该属性告诉您插入符号(光标、插入点)在哪里以及您应该插入特殊字符的位置。如果您将对象设置为符合“ ”协议的委托,这应该可以工作。UITextInput

于 2012-11-21T21:22:57.917 回答