Earlier in this chapter, you learned that NSApplication, NSWindow, and NSView share a common parent class: NSResponder. This class plays a central role in the AppKit event handling system, as it declares the interface to any class that can respond to events.
To handle events, it is often sufficient to understand how to create a suitable subclass of NSResponder (a custom view, for example) and implement the relevant methods (e.g., mouseDown:). The application framework is responsible for ensuring that the appropriate method is invoked on the right object.
The event handling architecture is built on three major ideas: event messages, action messages, and the responder chain. As discussed earlier, events enter an application through the window server and are dispatched by NSApplication, with the method sendEvent:, to forward the event to the appropriate object.
The event model also deals with action messages. Objects, usually interface controls, create actions within the application that can be routed to a target object. The method used to dispatch action messages is NSApplication's sendAction:to:from:. The sendAction: parameter is a selector for the action method to be invoked in the target, the to: parameter is the action's target, and from: is the sender of the action. It is possible that the target is unspecified, in which case the action is sent up the full responder chain, and the first responder object that implements the message responds to it.
As for event handling, a window may be the key window, the main window, or both. At any point in time, there is only one key and one main window (they may be the same). NSApplication always sends mouse and key events to the key window. NSWindow implements the method sendEvent: to route the event to the proper view within the window. This view is the most tightly nested view within the hierarchy over which the event occurred. If the view does not handle the event, then the event is sent to the view's next responder, which is usually its superview.
Action messages, on the other hand, are sent first to the key window's first responder, and follow the responder chain up to the window's content view. If no part of the responder chain handles the action, the window object and window delegate are given a chance to respond to the action. If the main window differs from the key window, the process repeats for the main window. Finally, if the main window and its responder chain do not respond to the action, the NSApplication object and its delegate are given a chance to respond. Thus, mouse and key events are always directed to the key window. If the key window is different from the main window, the main window is not given a chance to handle mouse and key events.
Every event responder method has the event object as its single argument. From this object, you can extract relevant information about the event, such as where the mouse is located in the window, or which key on the keyboard was pressed. Example 3-5 shows how to work with mouse-generated events.
- (void)mouseDown:(NSEvent *)theEvent { NSPoint winLoc = [theEvent locationInWindow]; NSPoint viewLoc = [self convertPoint:winLoc fromView:nil]; }
In this example, the locationInWindow method returns an NSPoint object, which gives the location of the event in the window's coordinate system. In the next line, you use the NSView method, convertPoint:fromView:, to convert the point from the coordinate system of one view to the coordinate system of the receiver. By passing nil as the second argument, you convert the point from the base coordinate system of the window to that of the receiver view.
Responding to keyboard events is similar, although the important characteristic is not the location, but the key that is pressed. Example 3-6 shows how to respond to key events. To discover which specific key was pressed, use the characters method.
- (void)keyDown:(NSEvent *)theEvent { NSString *key = [theEvent characters]; if ( [key isEqualToString:@"c"] ) { // Handle event here for the 'c' key pressed } }
The final piece of the event modelthe glue that holds it all togetheris the responder chain. The responder chain gives objects that may potentially respond to an event or action a chance to do so. This results in an extremely flexible response model, as the originator of the event or action is decoupled from the ultimate destination.
The responder chain is a series of linked NSResponder objects. When an event or an action occurs, it is dispatched to what is known as the first responder, which is given the first chance to respond to the event or action. If the first responder is incapable of responding, then the next responder object in the chain is given a chance to respond. Events and actions are sent up the responder chain until an object is found that can respond to the message. Typically the Application Kit picks the first responder automatically in response to normal user interaction with the interface. In other words, the object that is clicked or that receives typing is the first responder.
When constructing an interface in Interface Builder, you typically assign a specific object as the target for a control's action. Interface Builder provides a mechanism for actions to be sent to the responder chain, rather than a specific target object. This is done by connecting an object's action to the First Responder proxy object in the nib window.
First Responder represents nil. If a control has a value for its target, it sends its action directly to that target. If the target is nil, this results in a "nil-targeted-action", denoting that the action should be passed to the responder chain (equivalent to passing nil as the message recipient in NSApplication's sendAction:to:from:).
The responder chain pattern is especially convenient when your interface and controller classes are split among several nib files. Using the First Responder in Interface Builder lets you send actions from a control in one nib to an object in another. This is common when working with document-based applications that have the main menu in one nib and the document interface and class in another.