Required Methods for Text Changes in Custom NSTextView

267 views Asked by At

I have a custom NSTextView, and I want to be sure to send all of the proper messages and notifications when making changes to the text programmatically. I mostly care about undo/redo registration, but I'd generally like to do things the "right" way. Anyway …

The messages I'm aware of are:

  • -shouldChangeTextInRange:replacementString:
  • -didChangeText

If I understand the documentation correctly, before any changes are made to the text view's textStorage object, one needs to send the -shouldChangeTextInRange:replacementString: message to make sure a new undo group is opened (and inform any delegates of the commencement of editing). If YES is returned, the changes can be made to the text. Once all of the changes have been applied, the -didChangeText message needs to be sent to close the undo group (and again, notify any observers). Is this right?

When modifying existing characters (or attributes) those instructions make sense to me. I'm acting on an existing range of text, so it's easy to know what to send for the affectedCharRange parameter. What about when I need to insert something?

Let's say I want to insert a random word at the current insertion point index. Do I need to send the -shouldChangeTextInRange:replacementString: message? I'm not modifying existing characters, but I am adding new characters to the existing characters.

If I do need to send this message, what range do I use for the affectedCharRange argument? Whenever I try to send the new computed range of the to-be-inserted text, I get "Range Out of Bounds" errors, which makes sense, considering the text view's length has yet to change. Do I just send the range for the insertion point with an empty length (e.g. self.selectedRange when nothing's selected)?

For example:

- (void)insertRandomWord:(id)sender
{
    NSAttributedString *randomAttrStr = [self randomAttributedString];
    BOOL shouldChangeText = [self shouldChangeTextInRange:shouldThisBeTheSelectedRange // <-- WTF, mate?
                                        replacementString:randomAttrStr.string];
    if ( shouldChangeText ) {

        [self.textStorage insertAttributedString:randomAttrStr
                                         atIndex:self.selectedRange.location];

        // This should always get called, right?
        [self didChangeText];

        // Is this where I would set the typing attributes?
        self.typingAttributes = [randomAttrStr attributesAtIndex:0 effectiveRange:NULL];
    }
}

I've gone as far as to create a method that takes a range and a block as arguments, so I don't accidentally forget to call something. Is this a good idea or not?

- (void)changeTextInRange:(NSRange)range replacementString:(NSString *)replacementString usingBlock:(void (^)(void))block
{
    BOOL change = [self shouldChangeTextInRange:range replacementString:replacementString];
    if ( change ) {
        block();
        [self didChangeText];
    }
}

Last, but not least, do I need to call the NSTextStorage "editing" methods, too? The methods I'm referring to are:

  • -beginEditing:
  • -endEditing:

The documentation discusses calling those when one subclasses NSTextStorage, but I'm a little confused as to whether those messages need to be sent, too. It doesn't really say whether or not to use -fixAttributesInRange:, but I know the -endEditing message calls that to do cleanup after an editing operation.

I apologize for the discombobulated mess of questions. I'm just super tired and confused, and Apple's documentation has been … lacking. Any tips, pointers, and/or suggestions would be greatly appreciated. Thanks!

0

There are 0 answers