Cocoa Event Handling Guide

Event Handling Basics

Some tasks found in event-handling code are common to events of more than one type. The following sections describe these basic event-handling tasks. Some of the information presented in this chapter is discussed at length, but using different examples, in the Creating a Custom View chapter of View Programming Guide.

Preparing a Custom View for Receiving Events

Although any type of responder object can handle events, NSView objects are by far the most common recipient of events. They are typically the first objects to respond to both mouse events and key events, often changing their appearance in reaction to those events. Many Application Kit classes are implemented to handle events, but by itself NSView does not. When you create your own custom view and you want it to respond to events, you have to add event-handling capabilities to it.

Implement one or more NSResponder methods to handle events of particular types.

If your view is to handle action messages passed to it via the responder chain, implement the appropriate action methods.

A view that is first responder accepts key events and action messages before other objects in a window. It also typically takes part in the key loop (see Keyboard Interface Control). By default, view objects refuse first-responder status by returning NO in acceptsFirstResponder. If you want your custom view to respond to key events and actions, your class must override this method to return YES:

- (BOOL)acceptsFirstResponder {

return YES;

}

Note: A view that becomes first responder in order to respond to key events or action messages should reflect this status by drawing a focus ring around itself. The focus ring indicates that the object is the current first responder for key events.

The second item in the list above—overriding an NSResponder method to handle an event—is of course a large subject and what most remaining chapters in this document are about. But there are a few basic guidelines to consider for event messages:

If necessary, examine the passed-in NSEvent object to verify that it’s an event you handle and, if so, find out how to handle it.

Call the appropriate NSEvent methods to help make this determination. For example, you might see which modifier keys were pressed (modifierFlags), find out whether a mouse-down event is a double- or triple-click (clickCount), or, for key events, get the associated characters (characters and charactersIgnoringModifiers).

If your implementation of an NSResponder method such as mouseDown: completely handles an event it should not invoke the superclass implementation of that method.

NSView inherits the NSResponder implementations of the methods for handling mouse events; in these methods, NSResponder simply passes the message up the responder chain. One of these objects in the responder chain can implement a method in a way that ensures your custom view won’t see subsequent related mouse events. Because of this, custom NSView objects should not invoke super in their implementations of NSResponder mouse-event-handling methods such as mouseDown:, mouseDragged: and mouseUp: unless it is known that the inherited implementation provides some needed functionality.

If you do not handle the event, pass it on up the responder chain. Although you can directly forward the message to the next responder, it’s generally better to forward the message to your superclass. If your superclass doesn’t handle the event, it forwards the message to its superclass, and so on, until NSResponder is reached. By default NSResponder passes all event messages up the responder chain. For example, instead of this:

- (void)mouseDown:(NSEvent *)theEvent {

// determine if I handle theEvent

// if not...

[[self nextResponder] mouseDown:theEvent];

}

do this:

- (void)mouseDown:(NSEvent *)theEvent {

// determine if I handle theEvent

// if not...

[super mouseDown:theEvent];

}

If your subclass needs to handle particular events some of the time—only some typed characters, perhaps—then it must override the event method to handle the cases it’s interested in and to invoke the superclass implementation otherwise. This allows a superclass to catch the cases it’s interested in, and ultimately allows the event to continue on its way along the responder chain if it isn’t handled.

If you deliberately want to bypass the superclass implementation of an NSResponder method—say your superclass is NSForm—and yet pass an event up the responder chain, resend the message to [self nextResponder].

Implementing Action Methods

Action messages are typically sent by NSMenuItem objects or by NSControl objects. The latter objects usually work together with one or more NSCell objects. The cell object stores a method selector identifying the action message to be sent and a reference to the target object. (A menu item encapsulates its own action and target data.) When a menu item or control object is clicked or otherwise manipulated, it gets the action selector and target object—in the control’s case, from one of its cells—and sends the message to the target.

You can set the action selector and target programmatically using, respectively, the methods setAction: and setTarget: (declared by NSActionCell, NSMenuItem, and other classes). However, you typically specify these in Interface Builder. In this application, you connect a control object to another object (the target) in the nib file by Control-dragging from the control object to the target and then selecting the action method of the target to invoke. If you want the action message to be untargeted, you can either set the target to nil programmatically or, in Interface Builder, make a connection between the menu item or control and the First Responder icon in the nib file window, as shown in Figure 3-1.

Figure 3-1 Connecting an untargeted action in Interface Builder

From Interface Builder you can generate a header file and implementation file for your Xcode project that include a declaration and skeletal implementation, respectively, for each action method defined for a class. These Interface Builder–defined methods have a return “value” of IBAction, which acts as a tag to indicated that the target-action connection is archived in a nib file. You can also add the declarations and skeletal implementations of action methods yourself; in this case, the return type is void.) The remaining required part of the signature is a single parameter typed as id and named (by convention) sender.

Listing 3-1 illustrates a straightforward implementation of an action method that toggles a clock’s AM-PM indicator when a user clicks a button.

Listing 3-1 Simple implementation of an action method

- (IBAction)toggleAmPm:(id)sender {

[self incrementHour:12 andMinute: 0];

}

Action methods, unlike NSResponder event methods, don’t have default implementations, so responder subclasses shouldn’t blindly forward action messages to super. The passing of action messages up the responder chain in the Application Kit is predicated merely on whether an object responds to the method, unlike with the passing of event messages. Of course, if you know that a superclass does in fact implement the method, you can pass it on up from your subclass, but otherwise don’t.

An important feature of action messages is that you can send messages back to sender to get further information or associated data. For example, the menu items in a given menu might have represented objects assigned to them; for example, a menu item with a title of “Red” might have a represented object that is an NSColor object. You can access this object by sending representedObject to sender.

You can take the messaging-back feature of action methods one step further by using it to dynamically change sender’s target, action, title, and similar attributes. Here is a simple test case: You want to control a progress indicator (an NSProgressIndicator object) with a single button; one click of the button starts the indicator and changes the button’s title to “Stop”, and then the next click stops the indicator and changes the title to back to “Start”. Listing 3-2 shows one way to do this.

However, this implementation requires that the target and action information be set back to what they were after the redirected action message is sent via performClick:. You could simplify this implementation by invoking directly sendAction:to:from:, the method used by the application object (NSApp) to dispatch action messages (see Listing 3-3).

In keyboard action messages, an action method is invoked as a result of a particular key-press being interpreted through the key bindings mechanism. Because such messages are so closely connected to specific key events, the implementation of the action method can get the event by sending currentEvent to NSApp and then query the NSEvent object for details. Listing 3-7 gives an example of this technique. See The Path of Key Events for a summary of keyboard action messages; also see Key Bindings for a description of that mechanism.

Getting the Location of an Event

You can get the location of a mouse or tablet-pointer event by sending locationInWindow to an NSEvent object. But, as the name of the method denotes, this location (an NSPoint structure) is in the base coordinate system of a window, not in the coordinate system of the view that typically handles the event. Therefore a view must convert the point to its own coordinate system using the method convertPoint:fromView:, as shown in Listing 3-4.

Listing 3-4 Converting a mouse-dragged location to be in a view’s coordinate system

- (void)mouseDragged:(NSEvent *)event {

NSPoint eventLocation = [event locationInWindow];

center = [self convertPoint:eventLocation fromView:nil];

[self setNeedsDisplay:YES];

}

The second parameter of convertPoint:fromView: is nil to indicate that the conversion is from the window’s base coordinate system.

Keep in mind that the locationInWindow method is not appropriate for key events, periodic events, or any other type of event other than mouse and tablet-pointer events.

Testing for Event Type and Modifier Flags

On occasion you might need to discover the type of an event. However, you do not need to do this within an event-handling method of NSResponder because the type of the event is apparent from the method name: rightMouseDragged:, keyDown:, tabletProximity:, and so on. But from elsewhere in an application you can always obtain the currently handled event by sending currentEvent to NSApp. To find out what type of event this is, send type to the NSEvent object and then compare the returned value with one of the NSEventType constants. Listing 3-5 gives an example of this.

// If we find anything other than a mouse down or dragged we are done.

return YES;

}

A common test performed in event-handling methods is finding out whether specific modifier keys were pressed at the same moment as a key-press, mouse click, or similar user action. The modifier key usually imparts special significance to the event. The code example in Listing 3-6 shows an implementation of mouseDown: that determines whether the Command key was pressed while the mouse was clicked. If it was, it rotates the receiver (a view) 90 degrees. The identification of modifier keys requires the event handler to send modifierFlags to the passed-in event object and then perform a bitwise-AND operation on the returned value using one or more of the modifier mask constants declared in NSEvent.h.

Listing 3-6 Testing for modifier keys pressed—event method

- (void)mouseDown:(NSEvent *)theEvent {

// if Command-click rotate 90 degrees

if ([theEvent modifierFlags] & NSCommandKeyMask) {

[self setFrameRotation:[self frameRotation]+90.0];

[self setNeedsDisplay:YES];

}

}

You can test an event object to find out if any from a set of modifier keys was pressed by performing a bitwise-OR with the modifier-mask constants, as in Listing 3-7. (Note also that this example shows the use of the currentEvent method in a keyboard action method.)

Listing 3-7 Testing for modifier keys pressed—action method

- (void)moveLeft:(id)sender {

// Use left arrow to decrement the time. If a shift key is down, use a big step size.

BOOL shiftKeyDown = ([[NSApp currentEvent] modifierFlags] &

(NSShiftKeyMask | NSAlphaShiftKeyMask)) !=0;

[self incrementHour:0 andMinute:-(shiftKeyDown ? 15 : 1)];

}

Further, you can look for certain modifier-key combinations by linking the individual modifier-key tests together with the logical AND operator (&&). For example, if the example method in Listing 3-6 were to look for Command-Shift-click rather than Command-click, the complete test would be the following:

Responder-Related Tasks

The following sections describe tasks related to the first-responder status of objects.

Determining First-Responder Status

Usually an NSResponder object can always determine if it's currently the first responder by asking its window (or itself, if it's an NSWindow object) for the first responder and then comparing itself to that object. You ask an NSWindow object for the first responder by sending it a firstResponder message. For an NSView object, this comparison would look like the following bit of code:

if ([[self window] firstResponder] == self) {

// do something based upon first-responder status

}

A complication of this simple scenario occurs with text fields. When a text field has input focus, it is not the first responder. Instead, the field editor for the window is the first responder; if you send firstResponder to the NSWindow object, an NSTextView object (the field editor) is what is returned. To determine if a given NSTextField is currently active, retrieve the first responder from the window and find out it is an NSTextView object and if its delegate is equal to the NSTextField object. Listing 3-8 shows how you might do this.

The control that a field editor is editing is always the current delegate of the field editor, so (as the example shows) you can obtain the text field by asking for the field editor's delegate. For more on the field editor, see Text Fields, Text Views, and the Field Editor.

Setting the First Responder

You can programmatically change the first responder by sending makeFirstResponder: to an NSWindow object; the argument of this message must be a responder object (that is, an object that inherits from NSResponder). This message initiates a kind of protocol in which one object loses its first responder status and another gains it.

makeFirstResponder: always asks the current first responder if it is ready to resign its status by sending it resignFirstResponder.

If the current first responder returns NO when sent this message, makeFirstResponder: fails and likewise returns NO.

A view object or other responder may decline to resign first responder status for many reasons, such as when an action is incomplete.

If the current first responder returns YES to resignFirstResponder, then the new first responder is sent a becomeFirstResponder message to inform it that it can be the first responder.

This object can return NO to reject the assignment, in which case the NSWindow itself becomes the first responder.

Figure 3-2 and Figure 3-3 illustrate two possible outcomes of this protocol.

Figure 3-2 Making a view a first responder—current view refuses to resign statusFigure 3-3 Making a view a first responder—new view becomes first responder

Listing 3-9 shows a custom NSCell class (in this case a subclass of NSActionCell) implementing resignFirstResponder and becomeFirstResponder to manipulate the keyboard focus ring of its superclass.

You can also set the initial first responder for a window—that is, the first responder set when the window is first placed onscreen. You can set the initial first responder programmatically by sending setInitialFirstResponder: to an NSWindow object. You can also set it when you create a user interface in Interface Builder. To do this, complete the following steps.

Control-drag from the window icon in the nib file window to an NSView object in the user interface.

In the Connections pane of the inspector, select the initialFirstResponder outlet and click Connect.

After awakeFromNib is called for all objects in a nib file, NSWindow makes the first responder whatever was set as initial first responder in the nib file. Note that you should not send setInitialFirstResponder: to an NSWindow object in awakeFromNib and expect the message to be effective.