Search code examples
macoscocoansattributedstringnstextfieldnstextview

Restore the visual state of an NSAttributedString after having clicked on it


I need to restore the visual state of an NSAttributedString after having clicked on it.

My NSAttributedString contains links attributed to ranges.

In this example the text "@user" has a a link to "htpp://somesite.com/":

let text = "Hey @user!"

let attr = NSMutableAttributedString(string: text)
let range = NSRange(location: 4, length: 5)
attr.addAttribute(NSForegroundColorAttributeName, value: NSColor.orange, range: range)
attr.addAttribute(NSLinkAttributeName, value: "htpp://somesite.com/", range: range)

let tf = NSTextField(frame: NSRect(x: 0, y: 0, width: 200, height: 50))
tf.allowsEditingTextAttributes = true
tf.isSelectable = true
tf.stringValue = text
tf.attributedStringValue = attr

It works well: click on "@user" in the text field, it launches the URL.

enter image description here

But once clicked, the attributed color disappears and is replaced by this blue one and an underline is added:

enter image description here

I can't find a solution to restore the original color once the string is clicked (or to avoid having this automatic change altogether).

I've seen this and this but there's no actual solution, and I can't integrate the pointed library to my project (I'd really like to not having to import any library, actually).

Note that my existing code is in Swift but I can use an Objective-C solution.


Solution

  • When the link is clicked, the text is displayed by the field editor. The default link text style in the field editor is blue and underlined.

    Solution 1: change the text style of the link in an override of setUpFieldEditorAttributes: in a subclass of NSTextFieldCell.

    - (NSText *)setUpFieldEditorAttributes:(NSText *)textObj {
        NSText *fieldEditor = [super setUpFieldEditorAttributes:textObj];
        if ([fieldEditor isKindOfClass:[NSTextView class]]) {
            NSMutableDictionary *linkAttributes = [((NSTextView *)fieldEditor).linkTextAttributes mutableCopy];
            linkAttributes[NSForegroundColorAttributeName] = [NSColor orangeColor];
            [linkAttributes removeObjectForKey:NSUnderlineStyleAttributeName];
            ((NSTextView *)fieldEditor).linkTextAttributes = linkAttributes;
        }
        return fieldEditor;
    }
    

    Side effect: the field editor is shared by all controls in the window and all controls will now show orange links.

    Solution 2: substitute your own field editor by using the fieldEditor:forObject: method or the windowWillReturnFieldEditor:toObject: delegate method of NSWindow. The text field has its own field editor and other controls won't have orange links. No subclasses of NSTextField or NSTextFieldCell required.

    Example: (AppDelegate is the delegate of the window)

    @interface AppDelegate ()
    
    @property (weak) IBOutlet NSWindow *window;
    @property (weak) IBOutlet NSTextField *textField;
    @property (nonatomic, strong) NSTextView *linkFieldEditor;
    
    @end
    
    @implementation AppDelegate
    
    - (NSTextView *)linkFieldEditor {
        if (!_linkFieldEditor) {
            _linkFieldEditor = [[NSTextView alloc] initWithFrame:NSZeroRect];
            _linkFieldEditor.fieldEditor = YES;
            NSMutableDictionary *linkAttributes = [_linkFieldEditor.linkTextAttributes mutableCopy];
            linkAttributes[NSForegroundColorAttributeName] = [NSColor orangeColor];
            [linkAttributes removeObjectForKey:NSUnderlineStyleAttributeName];
            _linkFieldEditor.linkTextAttributes = linkAttributes;
        }
        return _linkFieldEditor;
    }
    
    - (id)windowWillReturnFieldEditor:(NSWindow *)sender toObject:(id)client {
        if (client == self.textField)
            return self.linkFieldEditor;
        else
            return nil;
    }
    

    Solution 3: create a subclass of NSTextFieldCell, implement fieldEditorForView: and return your own field editor. This is similar to solution 2 but implemented by the cell instead of the window delegate.

    Documentation on the field editor: Text Fields, Text Views, and the Field Editor and Using a Custom Field Editor.