Search code examples
cocoanstextviewcore-text

Cocoa Text - refreshing text on-the-fly


In an app I'm working on, the user inputs plain text, and the app reformats the text by transforming it to an NSAttributedString, and displays it. This all happens live.

Currently, I'm doing the following on my NSTextView's textDidChange delegate method:

- (void)textDidChange:(NSNotification *)notification {

    // saving the cursor position
    NSInteger insertionPoint = [[[self.mainTextView selectedRanges] objectAtIndex:0] rangeValue].location;

    // this grabs the text view's contact as plain text
    [self updateContentFromTextView];

    // this creates an attributed strings and displays it
    [self updateTextViewFromContent];

    // resetting the cursor position
    self.mainTextView.selectedRange = NSMakeRange(insertionPoint, 0);
}

While this mostly works, it's not ideal. The text seems to blink for a split second (you especially notice it on the red dots under spelling errors), and when the cursor was previously near one of the edges of the visible rect, it the scroll position gets reset. In my case, this is a very much undesirable side-effect.

So my question is: Is there a better way of doing what I'm trying to do?


Solution

  • I think you have a slight misconception of how an NSTextView works. The user never enters a "plain string", the data store of an NSTextView is always an NSTextStorage object, which is a subclass of NSMutableAttributedString.

    What you need to do is add/remove attributes to the existing attributed string that the user is editing, rather than replacing the entire string.

    You should also not make changes to the string in the ‑textDidChange: delegate method, as changing the string from that method can cause another change notification.

    Instead, you should implement the delegate method ‑textStorageDidProcessEditing:. This is called whenever the text changes. You can then make modifications to the string like so:

    - (void)textStorageDidProcessEditing:(NSNotification*)notification
    {
        //get the text storage object from the notification
        NSTextStorage* textStorage = [notification object];
    
        //get the range of the entire run of text
        NSRange aRange = NSMakeRange(0, [textStorage length]);
    
        //for example purposes, change all the text to yellow
    
        //remove existing coloring
        [textStorage removeAttribute:NSForegroundColorAttributeName range:aRange];
    
        //add new coloring
        [textStorage addAttribute:NSForegroundColorAttributeName 
                            value:[NSColor yellowColor] 
                            range:aRange];
    }