Как реализовать отмену / повтор с программным изменением textValue NSTextView? - PullRequest
10 голосов
/ 25 ноября 2011

Я создал простое демонстрационное приложение с 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 ]

11 голосов
/ 01 августа 2012

Нет необходимости добавлять NSUndoManager здесь, просто дайте NSTextView сделать работу.

Вам просто нужно убедиться, что вы вызываете только методы более высокого уровня NSTextView, начиная с insert ... и не устанавливая текст / строку textView или textStorage напрямую:

    [self.textView insertText:newString];

Если вам абсолютно необходимо использовать setString или другие методы более низкого уровня, то вам просто нужно добавить необходимые методы, обрабатывающие делегирование textDidChange: -shouldChangeTextInRange: replaceString и -didChangeText ( что делается путем вставки ... методы кстати):

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];
}

Это автоматически позволяет активировать undoManager из NSTextView. Я думаю, что undoManager готовит отмену группировки в shouldChangeTextInRange: и вызывает отмену в didChangeText:.

5 голосов
/ 25 ноября 2011

-setString: является унаследованным методом от NSText. Чтобы справиться с этим, используя NSTextView методы только для обработки отмены, просто сделайте это:

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

Внесение изменений в текст таким образом полностью исключает возможность использования менеджера отмены.

1 голос
/ 06 апреля 2016

Допустим, вы хотите, чтобы ваш 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
}
...