Question

I cannot find a simple example of how to use an NSTextField to edit it's contents in place.

Exactly like in the Finder - you're able to click, and with a short delay the text field becomes editable.

It seems like it's some combination of the textField, it's cell, and the fieldEditor? Problem is I can't find the most basic example of how to do it.

I've tried subclassing NSTextField with a couple different tests but it hasn't worked:

#import "GWTextField.h"
@implementation GWTextField
- (id) initWithFrame:(NSRect)frameRect {
    self = [super initWithFrame:frameRect];
    return self;
}
- (void) mouseDown:(NSEvent *)theEvent {
    [super mouseDown:theEvent];
    [self.cell editWithFrame:self.frame inView:self.superview editor:[self.cell fieldEditorForView:self] delegate:self event:theEvent];
    //[self setEditable:TRUE];
    //[self setSelectable:TRUE];
    //[self selectText:nil];
    [NSTimer scheduledTimerWithTimeInterval:.3 target:self selector:@selector(edit:) userInfo:nil repeats:FALSE];
}
- (void) edit:(id) sende {
    NSLog(@"edit");
    [[NSApplication sharedApplication].mainWindow makeFirstResponder:self];
    [self selectText:nil];
}
@end

Any ideas?

Was it helpful?

Solution 3

I came up with a better solution to the edit in place problem. I believe this is how to properly do edit in place with NSCell. Please show and tell if this is wrong.

#import <Cocoa/Cocoa.h>
@interface EditTextField : NSTextField <NSTextDelegate>
@end

---

#import "EditTextField.h"

@implementation EditTextField

- (void) mouseDown:(NSEvent *)theEvent {
    if(theEvent.clickCount == 2) {
        self.editable = TRUE;
        NSText * fieldEditor = [self.window fieldEditor:TRUE forObject:self];
        [self.cell editWithFrame:self.bounds inView:self editor:fieldEditor delegate:self event:theEvent];
    } else {
        [super mouseDown:theEvent];
    }
}

- (void) cancelOperation:(id)sender {
    [self.cell endEditing:nil];
    self.editable = FALSE;
}

- (BOOL) textView:(NSTextView *) textView doCommandBySelector:(SEL) commandSelector {
    NSString * selector = NSStringFromSelector(commandSelector);
    if([selector isEqualToString:@"insertNewline:"]) {
        NSText * fieldEditor = [self.window fieldEditor:TRUE forObject:self];
        [self.cell endEditing:fieldEditor];
        self.editable = FALSE;
        return TRUE;
    }
    return FALSE;
}

@end

OTHER TIPS

Here's another solution with no NSCell - one user pointed out that NSCell is deprecated and will at some point be gone.

#import <Cocoa/Cocoa.h>

@interface EditTextField : NSTextField <NSTextDelegate,NSTextViewDelegate,NSTextFieldDelegate>

@property BOOL isEditing;
@property BOOL commitChangesOnEscapeKey;
@property BOOL editAfterDelay;
@property CGFloat delay;

@end

----


#import "EditTextField.h"

@interface EditTextField ()
@property NSObject <NSTextFieldDelegate,NSTextViewDelegate> * userDelegate;
@property NSString * originalStringValue;
@property NSTimer * editTimer;
@property NSTrackingArea * editTrackingArea;
@end

@implementation EditTextField

- (id) initWithCoder:(NSCoder *)coder {
    self = [super initWithCoder:coder];
    [self defaultInit];
    return self;
}

- (id) initWithFrame:(NSRect)frameRect {
    self = [super initWithFrame:frameRect];
    [self defaultInit];
    return self;
}

- (id) init {
    self = [super init];
    [self defaultInit];
    return self;
}

- (void) defaultInit {
    self.delay = .8;
}

- (void) mouseDown:(NSEvent *) theEvent {
    if(theEvent.clickCount == 2) {
        [self startEditing];
    } else {
        [super mouseDown:theEvent];
        if(self.editAfterDelay) {
            [self startTracking];
            self.editTimer = [NSTimer scheduledTimerWithTimeInterval:.8 target:self selector:@selector(startEditing) userInfo:nil repeats:FALSE];
        }
    }
}

- (void) startTracking {
    if(!self.editTrackingArea) {
        self.editTrackingArea = [[NSTrackingArea alloc] initWithRect:self.bounds options:NSTrackingMouseEnteredAndExited|NSTrackingMouseMoved|NSTrackingActiveInActiveApp|NSTrackingAssumeInside|NSTrackingInVisibleRect owner:self userInfo:nil];
    }
    [self addTrackingArea:self.editTrackingArea];
}

- (void) mouseExited:(NSEvent *)theEvent {
    [self.editTimer invalidate];
    self.editTimer = nil;
}

- (void) mouseMoved:(NSEvent *) theEvent {
    [self.editTimer invalidate];
    self.editTimer = nil;
}

- (void) startEditing {
    id firstResponder = self.window.firstResponder;
    if([firstResponder isKindOfClass:[NSTextView class]]) {
        NSTextView * tv = (NSTextView *)firstResponder;
        if(tv.delegate && [tv.delegate isKindOfClass:[EditTextField class]]) {
            EditTextField * fr = (EditTextField *)tv.delegate;
            [fr stopEditingCommitChanges:FALSE clearFirstResponder:FALSE];
        }
    }
    if(self.delegate != self) {
        self.userDelegate = (NSObject <NSTextFieldDelegate,NSTextViewDelegate> *)self.delegate;
    }
    self.isEditing = TRUE;
    self.delegate = self;
    self.editable = TRUE;
    self.originalStringValue = self.stringValue;
    [self.window makeFirstResponder:self];
}

- (void) stopEditingCommitChanges:(BOOL) commitChanges clearFirstResponder:(BOOL) clearFirstResponder {
    self.editable = FALSE;
    self.isEditing = FALSE;
    self.delegate = nil;
    [self removeTrackingArea:self.editTrackingArea];
    if(!commitChanges) {
        self.stringValue = self.originalStringValue;
    }
    if(clearFirstResponder) {
        [self.window makeFirstResponder:nil];
    }
}

- (void) cancelOperation:(id) sender {
    if(self.commitChangesOnEscapeKey) {
        [self stopEditingCommitChanges:TRUE clearFirstResponder:TRUE];
    } else {
        [self stopEditingCommitChanges:FALSE clearFirstResponder:TRUE];
    }
}

- (BOOL) textView:(NSTextView *) textView doCommandBySelector:(SEL) commandSelector {
    BOOL handlesCommand = FALSE;
    NSString * selector = NSStringFromSelector(commandSelector);

    if(self.userDelegate) {

        if([self.userDelegate respondsToSelector:@selector(control:textView:doCommandBySelector:)]) {
            handlesCommand = [self.userDelegate control:self textView:textView doCommandBySelector:commandSelector];
        } else if([self.userDelegate respondsToSelector:@selector(textView:doCommandBySelector:)]) {
            handlesCommand = [self.userDelegate textView:textView doCommandBySelector:commandSelector];
        }

        if(!handlesCommand) {

            if([selector isEqualToString:@"insertNewline:"]) {
                [self stopEditingCommitChanges:TRUE clearFirstResponder:TRUE];
                handlesCommand = TRUE;
            }

            if([selector isEqualToString:@"insertTab:"]) {
                [self stopEditingCommitChanges:TRUE clearFirstResponder:FALSE];
                handlesCommand = FALSE;
            }
        }

    } else {

        if([selector isEqualToString:@"insertNewline:"]) {
            [self stopEditingCommitChanges:TRUE clearFirstResponder:TRUE];
            handlesCommand = TRUE;
        }

        if([selector isEqualToString:@"insertTab:"]) {
            [self stopEditingCommitChanges:TRUE clearFirstResponder:FALSE];
            handlesCommand = FALSE;
        }
    }

    return handlesCommand;
}

@end

I built a re-usable NSTextField subclass you can use for edit in place functionality. http://pastebin.com/QymunMYB

In my application I have two text fields - one non editable, and second, hidden, editable, and activates title editing by calling:

[self addSubview:windowTitle];
[windowTitleLabel removeFromSuperview]; 
[self.window makeFirstResponder:windowTitle];   

This is called from mouseUp: on view behind the label.

I don't remember why I needed to have two text fields (i didn't know Cocoa good that time), probably it will work even without label swapping.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top