简体   繁体   English

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

[英]How can I insert text into UITextField at the current cursor position?

I'm trying to use the UITextField's "return" key to insert a custom character.我正在尝试使用 UITextField 的“返回”键插入自定义字符。 Here's what my UITextFieldDelegate method looks like:这是我的 UITextFieldDelegate 方法的样子:

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

Unfortunately, this only works some of the time:不幸的是,这只在某些时候有效:

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

In the last case I would have expected "one¶|two".在最后一种情况下,我会期望“one¶|two”。

How do I ensure that the inserted text is always inserted at the cursor position?如何确保插入的文本始终插入光标位置?

Thanks.谢谢。

The problem is when you tap the return key on the keyboard, the text field sets the selected range (the cursor position) to the end of its text before it sends you the textFieldShouldReturn: message.问题是当您点击键盘上的返回键时,文本字段向您发送textFieldShouldReturn:消息之前将所选范围(光标位置)设置为其文本的结尾。

You need to keep track of the cursor position so you can restore it to its prior position.您需要跟踪光标位置,以便将其恢复到之前的位置。 Let's say you have a reference to the text field in a property:假设您引用了属性中的文本字段:

@interface ViewController () <UITextFieldDelegate>

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

@end

You'll need an instance variable to hold the prior selected text range (from before the return key was tapped):您需要一个实例变量来保存先前选择的文本范围(从点击返回键之前开始):

@implementation ViewController {
    UITextRange *priorSelectedTextRange_;
}

Then you can write a method that saves the selected text range to the instance variable:然后,您可以编写一个方法,将选定的文本范围保存到实例变量中:

- (void)saveTextFieldSelectedTextRange {
    priorSelectedTextRange_ = self.textField.selectedTextRange;
}

and in textFieldShouldReturn: , before you insert the pilcrow, you can change the selected text range back to its prior value:textFieldShouldReturn: ,在插入 pilcrow 之前,您可以将选定的文本范围更改回其先前值:

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

But how can we make the system send the saveTextFieldSelectedTextRange message when we need it to?但是我们怎样才能让系统在需要的时候发送saveTextFieldSelectedTextRange消息呢?

  • The UITextFieldDelegate protocol doesn't have messages for changes to the selected range. UITextFieldDelegate协议没有关于所选范围更改的消息。

  • UITextField doesn't post any notifications for changes to the selected range. UITextField不会针对所选范围的更改发布任何通知。

  • The UITextInputDelegate protocol does have selectionWillChange: and selectionDidChange: messages, but the system sets the text field's inputDelegate to its own UIKeyboardImpl object when the text field begins editing, so we can't use the inputDelegate . UITextInputDelegate协议确实有selectionWillChange:selectionDidChange:消息,但是当文本字段开始编辑时,系统将文本字段的inputDelegate设置为其自己的UIKeyboardImpl对象,因此我们不能使用inputDelegate

  • Key-value observing on the text field's selectedTextRange property isn't reliable.对文本字段的selectedTextRange属性的键值观察不可靠。 In my testing on the iOS 6.0 simulator, I don't get a KVO message when I move the cursor from the middle to the end of the text by tapping the text field.在我对 iOS 6.0 模拟器的测试中,当我通过点击文本字段将光标从文本中间移动到末尾时,我没有收到 KVO 消息。

The only way I can think of to reliably track changes to the text field's selected range is by adding an observer to the run loop.我能想到的可靠跟踪文本字段选定范围更改的唯一方法是向运行循环添加一个观察者。 On every pass through the event loop, the observer runs before event processing, so it can grab the current selected range before it changes.每次通过事件循环时,观察者都会在事件处理之前运行,因此它可以在更改之前获取当前选定的范围。

So we actually need another instance variable, to hold the reference to our run loop observer:所以我们实际上需要另一个实例变量来保存对我们的运行循环观察者的引用:

@implementation ViewController {
    UITextRange *priorSelectedTextRange_;
    CFRunLoopObserverRef runLoopObserver_;
}

We create the observer in viewDidLoad :我们在viewDidLoad创建观察者:

- (void)viewDidLoad {
    [super viewDidLoad];
    [self createRunLoopObserver];
}

and we destroy it in both viewDidUnload and in dealloc :我们在viewDidUnloaddealloc销毁它:

- (void)viewDidUnload {
    [super viewDidUnload];
    [self destroyRunLoopObserver];
}

- (void)dealloc {
    [self destroyRunLoopObserver];
}

To create the observer, we need a plain old C function for it to call.要创建观察者,我们需要一个普通的旧 C 函数来调用。 Here's that function:这是那个函数:

static void runLoopObserverCallback(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
    __unsafe_unretained ViewController *self = (__bridge ViewController *)info;
    [self saveTextFieldSelectedTextRange];
}

Now we can actually create the observer and register it with the main run loop:现在我们可以实际创建观察者并将其注册到主运行循环中:

- (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);
}

and here's how we actually deregister the observer and destroy it:下面是我们如何取消注册观察者并销毁它:

- (void)destroyRunLoopObserver {
    if (runLoopObserver_) {
        CFRunLoopRemoveObserver(CFRunLoopGetMain(), runLoopObserver_, kCFRunLoopCommonModes);
        CFRelease(runLoopObserver_);
        runLoopObserver_ = NULL;
    }
}

This approach works in my testing on the iOS 6.0 simulator.这种方法适用于我在 iOS 6.0 模拟器上的测试。

What's happening here is that you're not keeping track of the insertion point, also known as the selection range.这里发生的情况是您没有跟踪插入点,也称为选择范围。

And to do that, you need to get somewhat deeper into the guts of what UITextField can do.要做到这一点,您需要更深入地了解 UITextField 可以做什么。

Using UITextInput (accessible as a protocol that UITextField uses) , you can fetch the " selectedTextRange " property which tells you where the caret (cursor, insertion point) is and that's where you should insert your special character. 使用 UITextInput(可作为 UITextField 使用的协议访问) ,您可以获取selectedTextRange ”属性,该属性告诉您插入符号(光标、插入点)的位置以及您应该插入特殊字符的位置。 This should work if you set your object to be a delegate that conforms to the " UITextInput " protocol.如果您将对象设置为符合“ UITextInput ”协议的委托,这应该有效。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM