Search code examples
objective-cmacosfirst-responder

manage FirstResponder in subclass of NSViewController


I have a TextViewController which is a subclass of NSViewController which controls a View containing a NSTextView

I attach an instance of TextViewController to an existing view with the following code

TextViewController *textViewer;

...

[[self view] addSubview:[textViewer view]]; // embed new TextView in our host view
[[textViewer view] setFrame:[[self view] bounds]];  // resize the controller's view to the host size
textViewer.delegate = self;
[split.window makeFirstResponder:textViewer];

This works quite well, and the TextViewController traps the keyDown Event to perform various actions.

I wanted to allow users to select text in the NSTextView to copy to clipboard. Unfortunately clicking in the NSTextView makes this the FirstResponder and stops TextViewController from responding to key presses.

I can force the FirstResponder back to my TextViewController, but this seems like a kludge.

- (void)textViewDidChangeSelection:(NSNotification *)aNotification {
    [self.view.window makeFirstResponder:self];
}

I know I could subclass NSTextView to trap the keyDown Event, but this doesn't seem much better.

I am sure there must be a more elegant way of doing this.

I added a subclass of NSTextView which just passes the keyDown to the controller

@protocol MyTextViewDelegate
- (BOOL)keyPressedInTextView:(NSEvent *)theEvent;
@end

@interface MyTextView : NSTextView
@property (assign) IBOutlet NSObject <MyTextViewDelegate> *delegate;
@end

...

@implementation MyTextView
@synthesize delegate;
- (void)keyDown:(NSEvent *)theEvent {
    if([self.delegate respondsToSelector:@selector(keyPressedInTextView:)]) {
        if([self.delegate keyPressedInTextView:theEvent])
            return;
        }
    [super keyDown:theEvent];
}
@end

Solution

  • Subclassing NSTextView sounds like the best solution for the problem you described. You want a text view that behaves like normal to select, copy, Cmd-A, etc. except that it also responds to special key presses you've defined. This is a standard use of a subclass. Trying to have the view controller handle things by playing games with the first responder will give you problems in various edge cases like the one you discovered.