Search code examples
objective-ccocoaappkit

How can I make an NSTextField containing attributed text but only copy plain text


I have an NSTextField subclass which uses a NSTextFieldCell subclass which contains an attributed string. I would like it if when the user copies text that it be copied to the pasteboard without the attributes.

I have tried subclassing NSTextView, overriding copy: and setting isFieldEditor to YES and returning this new editor from the cells fieldEditorForView method. While this copies only the plain text whenever it is used I have text drawn on text drawn on text (and so on ...) and if the underlying attributed string is changed by another control the field editor remains unmodified. When I do not use MyFieldEditor and let the NSTextFieldCell subclass use the default implementation this issue does not occur.

Is there a simpler solution to this problem?

Is there something additional that I need to override or receive delegate messages for?

MyFieldCell.m

- (NSTextView *)fieldEditorForView:(NSView *)controlView
{
    MyFieldEditor *editor = [[MyFieldEditor alloc] init];
    [super setUpFieldEditorAttributes:editor];

    return editor;
}

MyFieldEditor.m

@implementation MyFieldEditor

- (instancetype)init
{
    if ( (self = [super init]) )
    {
        [self setFieldEditor:YES];
    }

    return self;
}

- (NSString *)selectedString
{
    return [[self string] substringWithRange:[self selectedRange]];
}

- (void)copy:(id)sender
{
    [[NSPasteboard generalPasteboard] setString:[self selectedString] forType:NSPasteboardTypeString];
}

@end

Note: I am using ARC.

Display Issue Image

Drawing problem


Solution

  • Please find below what should be changed. Tested & works with Xcode 11.2.1 / macOS 10.15.2 with no display issues.

    a) No custom NSTextFieldCell and NSTextField are needed, so used just default

    b) Change to following in MyFieldEditor.m

    - (void)copy:(id)sender
    {
        [NSPasteboard.generalPasteboard declareTypes:@[NSPasteboardTypeString] owner:self];
        [NSPasteboard.generalPasteboard setString:[self selectedString] forType:NSPasteboardTypeString];
    }
    

    c) Add window delegate method substituting field editor for targeted textfield (this is the valid documented way to provide custom field editors)

    - (nullable id)windowWillReturnFieldEditor:(NSWindow *)sender toObject:(nullable id)client {
        if (client == self.textField) { // << in this case it is outlet
            return MyFieldEditor.new;
        }
        return nil;
    }
    

    Update:

    Overriding NSTextFieldCell as below and assigning it in XIB for targeted NSTextField instead of above NSWindow delegate method gives the same valid behaviour.

    @interface MyTextCell: NSTextFieldCell
    @end
    
    @implementation MyTextCell
    - (nullable NSTextView *)fieldEditorForView:(NSView *)controlView {
        id editor = MyFieldEditor.new;
        [super setUpFieldEditorAttributes:editor];
        return editor;
    }
    @end