Как реализовать отмену / возврат с программным изменением textValue NSTextView?

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

Undo / redo работает без проблем, если я изменяю текст вручную. Но если я изменю текст с помощью метода действия, отмена будет работать, как ожидалось, но повторить больше не будет (ничего не происходит), и диспетчер отмены, кажется, зашифрован ...

Хорошо - чтобы избежать проблем с 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

person user808667    schedule 24.11.2011    source источник


Ответы (3)


Нет необходимости добавлять сюда 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 готовит undoGrouping в shouldChangeTextInRange: и вызывает отмену в didChangeText :.

person auco    schedule 01.08.2012

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

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

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

person Rob Keniger    schedule 25.11.2011
comment
Спасибо, Роб, но это не решает мою проблему. Чтобы избежать проблем с NSTextView, я внес некоторые изменения и переместил отмену / повтор в модель - см. Отредактированный вопрос ... - person user808667; 25.11.2011

Предположим, вы хотите, чтобы ваш 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
}
person Vitalii Vashchenko    schedule 06.04.2016