Reputation: 1137
I'm trying to find a solution that allows me to get keydown events in a view controller. I do not believe a view controller is part of the responder chain by default.
I would appreciate a sample of how to go about this. I have had trouble finding documentation I can understand on how to add the VC to the responder chain and get the events.
Thanks.
Miek
Upvotes: 5
Views: 7540
Reputation: 21
In your NSWidow (or NSWindowController) class implementation set your view controller as the first responder:
[self makeFirstResponder:yourViewControllerInstance];
You must, of course, make your NSViewController class return YES to the acceptsFirstResponder message.
Upvotes: 2
Reputation: 12782
NSViewController
doesn't have a default way to do this. However, you can achieve this through subclassing NSView
. Here is the basic idea:
keyDown
send the event to the delegate. Another way is to post NSNotifications
in your keyDown
and observe and handle the notifications in your view controller. Other ways also exist.
NSView Subclass with Delegate method explained
Here is the delegation example with an NSView
subclass which declares a protocol in its header with one required method, an IBOutlet
id property that conforms to the protocol. The NSView
subclass calls this method to its delegate whenever it wants to. If the delegate is nil, that's fine in Cocoa. Also note, tangentially, I have added IB_Designable
and IBInspectable
to the view's color properties. This allows setting them in IB and requires the 10.10 SDK.
The app delegate has imported the NSView
subclass in the AppDelegate.m
implementation file and adopted the protocol in the AppDelegate
class extension at the top of the .m file. In the @implementation
section it also implements the method.
Also note in IB, I added an NSView
to the window, then set its class to the custom NSView
subclass in the inspector. Finally, I set its eventDelegate
IBOutlet
to the AppDelegate
proxy in IB.
Custom NSView subclass interface
#import <Cocoa/Cocoa.h>
@protocol EventDelegatingViewDelegate <NSObject>
- (void)view:(NSView *)aView didHandleEvent:(NSEvent *)anEvent;
@end
IB_DESIGNABLE
@interface EventDelegatingView : NSView
@property IBOutlet id<EventDelegatingViewDelegate> eventDelegate;
@property IBInspectable NSColor *fillColor;
@property IBInspectable NSColor *strokeColor;
@end
Custom NSView subclass implementation
#import "EventDelegatingView.h"
@implementation EventDelegatingView
- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent {return YES;}
// The following two methods allow a view to accept key input events. (literally they say, YES, please send me those events if I'm the center of attention.)
- (BOOL)acceptsFirstResponder {return YES;}
- (BOOL)canBecomeKeyView {return YES;}
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
[self.fillColor set];
NSRectFill(self.bounds);
[self.strokeColor set];
NSFrameRect(self.bounds);
}
// Notice these don't do anything but call the eventDelegate. I could do whatever here, but I didn't.
// The NICE thing about delgation is, the originating object stays in control of it sends to its delegate.
// However, true to the meaning of the word 'delegate', once you pass something to the delegate, you have delegated some decision making power to that delegate object and no longer have any control (if you did, you might have a bad code smell in terms of the delegation design pattern.)
- (void)mouseDown:(NSEvent *)theEvent
{
[self.eventDelegate view:self didHandleEvent:theEvent];
}
- (void)keyDown:(NSEvent *)theEvent
{
[self.eventDelegate view:self didHandleEvent:theEvent];
}
@end
App Delegate (and eventDelegate!) implementation
#import "AppDelegate.h"
// Import the view class and if there were other files that implement any protocol
#import "EventDelegatingView.h"
// Declare protocol conformance (or more accurately, not only import that protocol interface, but say you're going to implement it so the compiler can nag you if you don't)
@interface AppDelegate ()<EventDelegatingViewDelegate>
@property (weak) IBOutlet NSWindow *window;
// For the simplest demo app we don't even need this property.
@property IBOutlet EventDelegatingView *eventDelegatingView;
@end
@implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
// Insert code here to tear down your application
}
// It's all right here. Receive a reference to a view and a reference to an event, then do as you like with them.
#pragma mark - EventDelegatingViewDelegate
- (void)view:(NSView *)aView didHandleEvent:(NSEvent *)anEvent
{
NSString *interestingEventNote;
switch (anEvent.type) {
case NSKeyDown:
case NSKeyUp:
{
// For simplicity we won't try to figure out the modifier keys here.
interestingEventNote = [NSString stringWithFormat:@"%@ key was pressed.", anEvent.charactersIgnoringModifiers];
}
break;
case NSLeftMouseDown:
{
interestingEventNote = [NSString stringWithFormat:@"Left mouse down at point %@ in window", NSStringFromPoint(anEvent.locationInWindow)];
}
break;
default:
break;
}
NSLog(@"%@ %@ aView=%@\n note=%@", self, NSStringFromSelector(_cmd), aView, interestingEventNote?interestingEventNote:@"Nothing worth noting");
}
@end
And that's it for the power of delegation. Basically it's callbacks of sorts and is a great way to build a class to enable it to defer something elsewhere as wanted. Moving some business logic to the right place in a fairly lazy and open and loosely coupled way.
NOTE: My code example shows using the app delegate. But the principal is the same. A view controller is little more than a delegate and you can add as much or as little as you like.
Upvotes: 2
Reputation: 4107
You can implement something like this:
-(void) globalKeyDown: (NSNotification *) notification
method in your controller class, and then just add the observer in awakeFromNib...or loadView method of your controller
- (void)awakeFromNib
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(globalKeyDown:)
name:@"my_keyEvent"
object:nil];
}
in your view class
-(void)keyDown:(NSEvent *)theEvent
{
[[NSNotificationCenter defaultCenter] postNotificationName:@"my_keyEvent"
object:theEvent
userInfo:@{@"sender":self}];
}
Upvotes: 3