2011-11-24 23 views
6

Ho creato una semplice applicazione demo con un NSTextView e un pulsante, il fornito un NSTextViewDelegate al textView e ha aggiunto un'azione:Come implementare l'annullamento/ripristino con la modifica programmata del valore testo di un NSTextView?

- (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 lavori senza problemi, se cambio il testo a mano. Ma se cambio il testo con il metodo action, annulla il lavoro come previsto, ma il redo non funziona più (non succede nulla) e il gestore di annullamento sembra essere criptato ...

OK - per evitare problemi con NSTextView ho creato una classe del modello, associato a NSTextView e spostato l'annullamento/ripristino nel modello, ma questo mostra lo stesso comportamento di prima - cosa sto facendo male - dovrebbe essere semplice, non dovrebbe?

#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 

risposta

5

-setString: è un metodo ereditato da NSText. Per gestire questo utilizzando metodi NSTextView solo in modo che undo viene manipolato, solo fare questo:

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

Rendere modificare il testo in questo modo evita di pasticciare in giro con il manager di annullamento a tutti.

+0

Grazie Rob, ma questo non risolve il mio problema. Per evitare problemi con NSTextView ho apportato alcune modifiche e ho spostato l'annullamento/ripristino nel modello: vedere la domanda modificata ... – user808667

7

Non è necessario aggiungere NSUndoManager qui, basta che il NSTextView faccia il lavoro.

Hai solo bisogno di assicurarsi che si sta chiamando i metodi di livello superiore solo di NSTextView che iniziano con inserto ... e non impostare il testo/string della textView o textStorage direttamente:

[self.textView insertText:newString]; 

Se è assolutamente necessario utilizzare setString o altri metodi di livello inferiore, è sufficiente aggiungere i metodi richiesti per gestire la delega textDidChange: -shouldChangeTextInRange: replacementString e -didChangeText (operazione eseguita dal INSERT ... metodi btw):

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

Ciò consente automaticamente l'UndoManager di NSTextView calci nel Penso che l'UndoManager sta preparando un undoGrouping in shouldChangeTextInRange:. e invocando l'annullamento in didChangeText :.

1

Supponiamo che vogliate che NSTextView crei un nuovo gruppo di annullamento quando l'utente preme il tasto Invio (comportamento di Apple Pages). Quindi puoi digitare questo codice nella sottoclasse 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 
}