2011-11-24 15 views
6

Tôi tạo ra một ứng dụng demo đơn giản với một NSTextView và một nút, cung cấp một NSTextViewDelegate đến TextView và thêm một hành động:Làm thế nào để thực hiện hoàn tác/làm lại với sự thay đổi có lập trình của textValue của một 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 công trình không có vấn đề, nếu tôi thay đổi văn bản bằng tay. Nhưng nếu tôi thay đổi văn bản bằng phương thức hành động, hoàn tác hoạt động như mong đợi, nhưng làm lại không hoạt động nữa (không có gì xảy ra) và trình quản lý hoàn tác dường như bị xáo trộn ...

OK - để tránh các vấn đề với NSTextView tôi đã tạo một lớp mô hình, ràng buộc NSTextView với nó và di chuyển hoàn tác/làm lại mô hình, nhưng điều này cho thấy hành vi tương tự như trước - những gì tôi đang làm sai - điều này phải dễ dàng, phải không?

#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 

Trả lời

5

-setString: là phương pháp được kế thừa từ NSText. Để xử lý này sử dụng phương pháp NSTextView chỉ để undo được xử lý, chỉ cần làm điều này:

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

Làm cho văn bản thay đổi theo cách này tránh mucking về với người quản lý undo ở tất cả.

+0

Cảm ơn bạn Rob, nhưng điều đó không giải quyết được sự cố của tôi. Để tránh các vấn đề với NSTextView, tôi đã thực hiện một số thay đổi và di chuyển hoàn tác/làm lại đối với mô hình - xem câu hỏi đã chỉnh sửa ... – user808667

7

Không cần phải thêm NSUndoManager vào đây, chỉ cần để NSTextView thực hiện công việc.

Bạn chỉ cần chắc chắn rằng bạn đang gọi phương pháp mức độ cao hơn chỉ của NSTextView bắt đầu với chèn ... và không thiết lập các văn bản/chuỗi của TextView hoặc textStorage trực tiếp:

[self.textView insertText:newString]; 

Nếu bạn hoàn toàn cần sử dụng phương thức setString hoặc các phương thức mức thấp khác, thì bạn chỉ cần thêm các phương thức cần thiết để xử lý ủy nhiệm textDidChange: -shouldChangeTextInRange: replacementString-didChangeText (được thực hiện bởi chèn ... phương pháp 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]; 
} 

này sẽ tự động cho phép người undoManager của NSTextView kick in Tôi nghĩ rằng undoManager đang chuẩn bị một undoGrouping trong shouldChangeTextInRange:. và cách gọi các undo trong didChangeText :.

1

Giả sử bạn muốn NSTextView tạo nhóm hoàn tác mới khi người dùng nhấn phím Enter (hành vi Trang của Apple). Sau đó, bạn có thể nhập mã này vào lớp con NSTextView của mình:

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 
}