如何在更改NSTextView的textValue时实现撤销/重做?

10

我创建了一个简单的演示应用程序,其中包括一个NSTextView和一个按钮,提供了一个NSTextViewDelegate给textView,并添加了一个操作:

- (IBAction)actionButtonClicked:(id)sender {
    NSString *oldText = [[[self.textView textStorage] string] copy];
    NSString *newText = @"And... ACTION!";

    [[self.textView undoManager] registerUndoWithTarget:self.textView
                                               selector:@selector(setString:)
                                                 object:oldText];
    [[self.textView undoManager] setActionName:@"ACTION"];

    [self.textView setString:newText];
}

如果我通过手动更改文本,撤销/重做功能可以正常工作。但是如果我使用操作方法来更改文本,则撤销功能按预期工作,但重做功能不再起作用(没有任何反应),撤销管理器似乎被破坏了......

为了避免与NSTextView的问题,我创建了一个模型类,将NSTextView绑定到它上面,并将撤销/重做移动到了模型中,但是这显示出了与之前相同的行为 - 我做错了什么 - 这应该很容易解决,不是吗?

#import "GFTextStore.h"

@implementation GFTextStore

@synthesize textVal = textVal_;

-(void)changeText{
    if (!undoMan_) {
        undoMan_ = [[[NSApplication sharedApplication] mainWindow] undoManager];   
    }

    NSAttributedString *oldText = [self.textVal copy];
    NSString *tempStr = [[oldText string] stringByAppendingFormat:@"\n%@",[[NSCalendarDate date]description]];
    NSAttributedString *newText = [[NSAttributedString alloc] initWithString:tempStr];

    [self setTextVal:newText];


    [undoMan_ registerUndoWithTarget:self
                            selector:@selector(setTextVal:)
                              object:oldText];

    [undoMan_ setActionName:@"ACTION"];
}
@end
3个回答

16
不需要在这里添加NSUndoManager,只需让NSTextView完成它的工作即可。您只需要确保调用NSTextView的高级方法,从insert...开始,而不是直接设置textView或textStorage的文本/字符串。
    [self.textView insertText:newString];

如果你绝对需要使用 setString 或其他较低级别的方法,则只需添加所需的方法处理 textDidChange 代理:-shouldChangeTextInRange:replacementString-didChangeText(此操作由 insert... 方法完成):

if( [self.textView shouldChangeTextInRange:editedRange replacementString:editedString]) {
    // do some fancy stuff here…
    [self.textView.textStorage replaceCharactersInRange:editedRange
                          withAttributedString:myFancyNewString];
    // … and finish the editing with
    [self.textView didChangeText];
}

这会自动激活NSTextView的undoManager。我认为undoManager在shouldChangeTextInRange:中准备一个undoGrouping,并在didChangeText:中调用undo。


5

-setString:是从NSText继承而来的方法。要使用NSTextView方法处理它,以便处理撤销,只需执行以下操作:

[self.textView setSelectedRange:NSMakeRange(0, [[self.textView textStorage] length])];
[self.textView insertText:@"And… ACTION!"];

这种方式改变文本内容可以避免与撤销管理器交互的麻烦。

谢谢Rob,但那并没有解决我的问题。为了避免NSTextView的问题,我做了一些修改,并将撤消/恢复移动到了模型中-请参见编辑后的问题... - user808667

2

假设您希望您的NSTextView在用户按Enter键时创建新的撤销组(类似于Apple Pages行为)。那么您可以在NSTextView子类中输入以下代码:

override func shouldChangeTextInRange(affectedCharRange: NSRange, replacementString: String?) -> Bool {
    super.shouldChangeTextInRange(affectedCharRange, replacementString: replacementString)
    guard replacementString != nil else { return true }

    let newLineSet = NSCharacterSet.newlineCharacterSet()
    if let newLineRange = replacementString!.rangeOfCharacterFromSet(newLineSet) {

        // check whether it's a single character (user hit Return key)
        let singleCharRange = (replacementString!.startIndex)! ..< (replacementString!.startIndex.successor())!
        if newLineRange == singleCharRange {
            self.breakUndoCoalescing()
        }
    }

    return true
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接