【问题标题】:How can I replace text in UITextView with NSUndoManager support?如何使用 NSUndoManager 支持替换 UITextView 中的文本?
【发布时间】:2011-06-21 02:49:30
【问题描述】:

我希望能够以编程方式替换 UITextView 中的一些文本,因此我将此方法编写为 UITextView 类别:

- (void) replaceCharactersInRange:(NSRange)range withString:(NSString *)newText{

    self.scrollEnabled = NO;

    NSMutableString *textStorage = [self.text mutableCopy];
    [textStorage replaceCharactersInRange:range withString:newText];

    //replace text but undo manager is not working well
    [[self.undoManager prepareWithInvocationTarget:self] replaceCharactersInRange:NSMakeRange(range.location, newText.length) 
                                                                       withString:[textStorage substringWithRange:range]];
    NSLog(@"before replacing: canUndo:%d", [self.undoManager canUndo]); //prints YES
    self.text = textStorage; 
    NSLog(@"after replacing: canUndo:%d", [self.undoManager canUndo]); //prints NO
    if (![self.undoManager isUndoing])[self.undoManager setActionName:@"replace characters"];
    [textStorage release];

    //new range:
    range.location = range.location + newText.length;
    range.length = 0;
    self.selectedRange = range;

    self.scrollEnabled = YES;

}

它可以工作,但 NSUndoManager 在完成 self.text=textStorage 之后就停止工作(它似乎被重置)我找到了一个私有 API:-insertText:(NSString *) 可以完成这项工作,但如果我知道苹果是否会批准我的应用程序,谁知道用它。有什么方法可以用 NSUndoManager 支持在 UITextView 中替换文本?或者我在这里遗漏了什么?

【问题讨论】:

    标签: iphone ios objective-c uitextview nsundomanager


    【解决方案1】:

    实际上没有理由让任何黑客或自定义类别来实现这一点。您可以使用内置的 UITextInput 协议方法replaceRange:withText:。要插入文本,您只需执行以下操作:

    [textView replaceRange:textView.selectedTextRange withText:replacementText];
    

    这适用于 iOS 5.0。撤消会自动进行,不会出现奇怪的滚动问题。

    【讨论】:

    • 这不是 100% 正确的。 replaceRange:withText 将完成这项工作,并且 UITextInput 协议确实从 3.2 开始可用,但 UITextView 直到 5.0 才采用它。所以这个答案只适用于 OS5.0 及更高版本
    • 我根据@nacho4d 的评论更新了我的答案。很好的收获。
    • 是的,这就是 iOS 5 的做法。
    • 我已经更改了答案,因为我们已经在 iOS9 中了。也因为我发现在剪贴板包含像图像这样的大数据的情况下,使用剪贴板粘贴方法可能非常低效
    【解决方案2】:

    在偶然发现这个问题并把它弄成白发之后,我终于找到了一个令人满意的解决方案。这有点俗气,但它确实像一个魅力。这个想法是使用 UITextView 提供的工作复制和粘贴支持!我想你可能会感兴趣:

    - (void)insertText:(NSString *)insert
    {
        UIPasteboard *pboard = [UIPasteboard generalPasteboard];
    
        // Clear the current pasteboard
        NSArray *oldItems = [pboard.items copy];
        pboard.items = nil;
    
        // Set the new content to copy
        pboard.string = insert;
    
        // Paste
        [self paste: nil];
    
        // Restore pasteboard
        pboard.items = oldItems;
        [oldItems release];
    }
    

    编辑:当然,您可以自定义此代码以在文本视图中的任何位置插入文本。只需在调用paste 之前设置selectedRange

    【讨论】:

    • 此方法的唯一缺点是操作名称将是“撤消粘贴”而不是“撤消键入”。 (摇动设备时会出现此消息,因此会显示 NSUndoManager 警报)
    • @nacho4d 没错。但至少在 iPad 上,大多数用户实际上会使用键盘上不显示名称的撤消/重做按钮。您是否尝试使用NSUndoManagersetActionName: 给它一个自定义名称?也许您需要一个撤消组左右
    • 如何将“撤消粘贴”更改为“撤消键入”?我可以在 [self paste:nil] 之后调用 setActionName: 吗?
    • 我没试过,但如果可以更改,这似乎是唯一的选择。甚至可以在调用周围添加一个撤消分组。
    • 我确实尝试过更改操作名称,但没有成功。显然,目前还没有完美的解决方案。除此之外,这没关系:)
    【解决方案3】:

    不应该吗

    [[self.undoManager prepareWithInvocationTarget:self] replaceCharactersInRange:NSMakeRange(range.location, newText.length) 
                                                                       withString:[self.text substringWithRange:range]];
    

    你可能可以删除可变字符串,然后这样做,

    self.text = [self.text stringByReplacingCharactersInRange:range withString:newText];
    

    【讨论】:

    • 我觉得你是对的,应该是这样。我试过了,但这并不能解决问题。 self.text = textStorage 完成后,NSUndoManager 无法撤消。
    • 什么时候触发这个方法?我想重现这个。如果你能给我一些示例代码就更好了。
    • 我的 UIExView 有一个 inputAccessoryView,带有一些按钮。当按下按钮在文本中插入一些字符时,我会使用它
    • 我已经尝试了您的代码here,它似乎可以正常工作。你能告诉我我在做什么不同吗?
    • 你是在编辑的时候做的吗?这很奇怪......我想知道是否有办法查看堆积在 NSUndoManager 中的调用堆栈,这样我就可以确保一切都按预期工作......
    【解决方案4】:

    我已经为这个问题困扰了很长时间。我终于意识到,在对文本存储进行任何更改之前,您必须确保注册了撤消操作。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-07-02
      • 2012-02-24
      • 2014-02-27
      • 1970-01-01
      • 1970-01-01
      • 2012-02-22
      • 1970-01-01
      相关资源
      最近更新 更多