Cocoa and Objective-C: Handling Events

Exclusive offer: get 50% off this eBook here
Cocoa and Objective-C Cookbook

Cocoa and Objective-C Cookbook — Save 50%

Move beyond basic Cocoa development using over 70 simple and effective recipes for Mac OS X development

$23.99    $12.00
by Jeff Hawkins | June 2011 | Cookbooks

The trackpad is becoming more popular as an input device as all new Apple laptops now have a trackpad. Even desktop Macs have trackpad support with the addition of the Magic Trackpad. Adding gestures to your Cocoa application to support the trackpad is not difficult. The recipes in this article by Jeff Hawkins, author of Cocoa and Objective-C Cookbook, will show you how to add the three most popular gestures to your application. Specifically we will cover:

  • Interpreting the pinch gesture
  • Interpreting the swipe gesture
  • Interpreting the rotate gesture
  • Handling special keys
  • Working with NSResponder
  • Application-wide notifications with NotificationCenter

 

Cocoa and Objective-C Cookbook

Cocoa and Objective-C Cookbook

Move beyond basic Cocoa development using over 70 simple and effective recipes for Mac OS X development

        Read more about this book      

(For more resources related to this subject, see here.)

Some recipes in this article require Mac OS X Snow Leopard 10.6.

The Trackpad preferences allow you to easily adjust many gestures that will used in the following recipes.

To make sure that your trackpad is recognizing gestures, make sure that you have set the correct preferences to enable gesture support under the Trackpad System Preference.

Interpreting the pinch gesture

The pinch gesture is a gesture normally used for the zooming of a view or for changing the font size of text. In this recipe, we will create a custom view that handles the pinch gesture to resize a custom view.

Getting ready

In Xcode, create a new Cocoa Application and name it Pinch.

How to do it...

  1. In the Xcode project, right-click on the Classes folder and choose Add…, then choose New File… Under the MacOS X section, select Cocoa Class, then select Objective-C class. Finally, choose NSView in the Subclass of popup. Name the new file MyView.m
  2. Double-click on the MainMenu.xib file in the Xcode project. From Interface Builders Library palette, drag a Custom View into the application window.
  3. From Interface Builders Inspector's palette, select the Identity tab and set the Class popup to MyView.
  4. Choose Save from the File menu to save the changes that you have made.
  5. In Xcode, Add the following code in the drawRect: method of the MyView class implementation:

    NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:
    [self bounds] xRadius:8.0 yRadius:8.0];
    [path setClip];

    [[NSColor whiteColor] setFill];
    [NSBezierPath fillRect:[self bounds]];

    [path setLineWidth:3.0];

    [[NSColor grayColor] setStroke];
    [path stroke];

  6. Next, we need to add the code to handle the pinch gesture. Add the following method to the MyView class implementation:

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

    NSSize size = [self frame].size;
    size.height = size.height * ([event magnification] + 1.0);
    size.width = size.width * ([event magnification] + 1.0);

    [self setFrameSize:size];
    }

  7. Choose Build and Run from Xcode's toolbar to run the application.

How it works...

In our drawRect: method, we use Cocoa to draw a simple rounded rectangle with a three point wide gray stroke. Next, we implement the magnifyWithEvent: method. Because NSView inherits from NSResponder, we can override the magnifyWithEvent: method from the NSResponder class. When the user starts a pinch gesture, and the magnifyWithEvent: method is called, the NSEvent passed to us in the magnifyWithEvent: method which contains a magnification factor we can use to determine how much to scale our view. First, we get the current size of our view. We add one to the magnification factor and multiply by the frame's width and height to scale the view. Finally, we set the frame's new size.

There's more...

You will notice when running the sample code that our view resizes with the lower-left corner of our custom view remaining in a constant position. In order to make our view zoom in and out from the center, change the magnifyWithEvent: method's code to the following:

NSSize size = [self frame].size;
NSSize originalSize = size;
size.height = size.height * ([event magnification] + 1.0);
size.width = size.width * ([event magnification] + 1.0);

[self setFrameSize:size];

CGFloat deltaX = (originalSize.width - size.width) / 2;
CGFloat deltaY = (originalSize.height - size.height) / 2;

NSPoint origin = self.frame.origin;
origin.x = origin.x + deltaX;
origin.y = origin.y + deltaY;
[self setFrameOrigin:origin];

Basically, what we have done is moved our custom view's origin by the difference between the original size and the new size.

Interpreting the swipe gesture

The swipe gesture is detected when three or more fingers move across the trackpad. This gesture is often used to page through a series of images. In this recipe, we will create a custom view that interprets the swipe gesture in four different directions and displays the direction of the swipe in our custom view:

Cocoa and Objective-C: Handling Events

Getting ready

In Xcode, create a new Cocoa Application and name it Swipe.

How to do it...

  1. In the Xcode project, right-click on the Classes folder and choose Add…, then choose New File… Under the MacOS X section, select Cocoa Class, then select Objective-C class. Finally, choose NSView in the Subclass of popup. Name the new file MyView.m
  2. Double-click on the MainMenu.xib file in the Xcode project. From Interface Builders Library palette, drag a Custom View into the application window.
  3. From Interface Builders Inspector's palette, select the Identity tab and set the Class popup to MyView.
  4. Choose Save from the File menu to save the changes that you have made.
  5. In Xcode, open the MyView.h file and add the direction variable to the class interface:

    NSString *direction;

  6. Open the MyView.m file and add the following code in the drawRect: method of the MyView class implementation:

    NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:
    [self bounds] xRadius:8.0 yRadius:8.0];
    [path setClip];

    [[NSColor whiteColor] setFill];
    [NSBezierPath fillRect:[self bounds]];
    [path setLineWidth:3.0];

    [[NSColor grayColor] setStroke];
    [path stroke];

    if (direction == nil) {
    direction = @"";
    }

    NSAttributedString *string = [[[NSAttributedString alloc]
    initWithString:direction] autorelease];
    NSPoint point = NSMakePoint(([self bounds].size.width / 2) -
    ([string size].width / 2), ([self bounds].size.height / 2) -
    ([string size].height / 2));
    [string drawAtPoint:point];

  7. Add the following code to handle the swipe gesture:

    - (void)swipeWithEvent:(NSEvent *)event {
    if ([event deltaX] > 0) {
    direction = @"Left";
    } else if ([event deltaX] < 0) {
    direction = @"Right";
    } else if ([event deltaY] > 0) {
    direction = @"Up";
    } else if ([event deltaY] < 0){
    direction = @"Down";
    }
    [self setNeedsDisplay:YES];
    }

  8. In Xcode, choose Build and Run from the toolbar to run the application.

How it works...

As we did in the other recipes in this article, we draw a simple rounded rectangle in the drawRect: method of the view. However, we will also be drawing a string denoting the direction of the swipe in the middle of our view.

In order to handle the swipe gesture, we override the swipeWithEvent: method from the NSResponder class which NSView inherits. By inspecting the values of deltaX and deltaY of the NSEvent passed into the swipeWithEvent: method, we can determine the direction of the swipe. We set the direction string with the direction of the swipe so we can draw it in the drawRect: method. Finally, we call setNeedsDisplay:YES to force our view to redraw itself.

There's more...

You might have noticed that we do not need to override the acceptsFirstResponder: method in our view in order to handle the gesture events. When the mouse is located within our view, we automatically receive the gesture events. All we need to do is implement the methods for the gestures we are interested in.

Interpreting the rotate gesture

The rotate gesture can be used in any number of ways in a custom view. From rotating the view itself or simulating a rotating dial in a custom control. This recipe will show you how to implement the rotate gesture to rotate a custom view. Using your thumb and index finger, you will be able to rotate the custom view around its center:

Cocoa and Objective-C: Handling Events

Getting ready

In Xcode, create a new Cocoa Application and name it Rotate.

How to do it...

  1. In the Xcode project, right-click on the Classes folder and choose Add…, then choose New File… Under the MacOS X section, select Cocoa Class, then select Objective-C class. Finally, choose NSView in the Subclass of popup. Name the new file MyView.m.
  2. Double-click on the MainMenu.xib file in the Xcode project. From Interface Builders Library palette, drag a Custom View into the application window.
  3. From Interface Builders Inspector's palette, select the Identity tab and set the Class popup to MyView.
  4. Choose Save from the File menu to save the changes that you have made.
  5. In Xcode, add the following code in the drawRect: method of the MyView class implementation:

    NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:
    [self bounds] xRadius:8.0 yRadius:8.0];
    [path setClip];

    [[NSColor whiteColor] setFill];
    [NSBezierPath fillRect:[self bounds]];

    [path setLineWidth:3.0];

    [[NSColor grayColor] setStroke];
    [path stroke];

  6. Next we need to add the code to handle the rotate gesture. Add the following method to the MyView class implementation:

    - (void)rotateWithEvent:(NSEvent *)event {
    CGFloat currentRotation = [self frameCenterRotation];
    [self setFrameCenterRotation:(currentRotation + [event
    rotation])];
    }

  7. Choose Build and Run from Xcode's toolbar to test the application.

How it works...

As we did in the previous recipe, we will create a simple rounded rectangle with a three-point stroke to represent our custom view.

Our custom view overrides the rotateWithEvent: method from NSResponder to handle the rotation gesture. The rotation property of the NSEvent passed to us in the rotateWithEvent: contains the change in rotation from the last time the rotateWithEvent: method was called. We simply add this value to our view's frameCenterRotation value to get the new rotation.

There's more...

The value returned from NSEvent's rotation will be negative when the rotation is clockwise and positive when the rotation is counter-clockwise.

Cocoa and Objective-C Cookbook Move beyond basic Cocoa development using over 70 simple and effective recipes for Mac OS X development
Published: May 2011
eBook Price: $23.99
Book Price: $39.99
See more
Select your format and quantity:
        Read more about this book      

(For more resources related to this subject, see here.)

Handling special keys

Depending on your application, you may need to handle keyboard shortcuts using the function keys or other special keys on the keyboard. In this recipe, we will show you how to handle these special keys in a custom view by overriding the keyDown: method.

Getting ready

In Xcode create a new Cocoa Application and name it SpecialKeys.

How to do it...

  1. In the Xcode project, right-click on the Classes folder and choose Add…, then choose New File… Under the MacOS X section, select Cocoa Class, then select Objective-C class. Finally, choose NSView in the Subclass of popup. Name the new file MyView.m.
  2. Click on the MyView.h file to open it and add the message variable to the MyView class interface:

    NSString *message;

  3. Double-click on the MainMenu.xib file in the Xcode project. From Interface Builders Library palette, drag a Custom View into the application window.
  4. From Interface Builders Inspector's palette, select the Identity tab and set the Class popup to MyView.
  5. Choose Save from the File menu to save the changes that you have made.
  6. In Xcode, click on MyView.m to open the MyView implementation.
  7. Add the following awakeFromNib: method:

    - (void) awakeFromNib {
    message = @"";
    }

  8. Next, add the code for the drawRect: method of the MyView class implementation:

    NSBezierPath *path = [NSBezierPath bezierPathWithRect:[self
    bounds]];
    [[NSColor whiteColor] set];
    [path fill];

    [[NSColor grayColor] set];
    [path setLineWidth:3.0];
    [path stroke];

    NSAttributedString *attributedString = [[[NSAttributedString
    alloc] initWithString:message attributes:nil] autorelease];

    NSPoint point = NSMakePoint(([self bounds].size.width / 2) -
    ([attributedString size].width / 2),
    ([self bounds].size.height / 2) - ([attributedString
    size].height / 2));
    [attributedString drawAtPoint:point];

  9. Add the following acceptsFirstResponder method to MyView.m:

    - (BOOL) acceptsFirstResponder {
    return YES;
    }

  10. Add the following keyDown: method to MyView.m:

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

    if ([[theEvent characters] length] != 0) {
    NSString *characters = [theEvent characters];

    unichar key = [characters characterAtIndex:0];

    switch (key) {
    case NSF6FunctionKey:
    message = @"F6";
    break;
    case NSF7FunctionKey:
    message = @"F7";
    break;
    case NSUpArrowFunctionKey:
    message = @"Up";
    break;
    case NSDownArrowFunctionKey:
    message = @"Down";
    break;
    case NSLeftArrowFunctionKey:
    message = @"Left";
    break;
    case NSRightArrowFunctionKey:
    message = @"Right";
    break;
    case NSDeleteFunctionKey: // Forward delete (fn + delete on
    laptop)
    message = @"Forward Delete";
    break;
    case NSDeleteCharacter:
    message = @"Delete";
    default:
    break;
    }
    }

    [self setNeedsDisplay:YES];
    }

  11. Choose Build and Run from Xcode's toolbar to run the application.

How it works...

First we will create a simple rectangle with a three-point stroke to represent our custom view.

Next, we override the acceptsFirstResponder method from NSResponder and return YES. This signals that we would like to be a first responder in the responder chain to handle keyboard events.

To handle the special keys on the keyboard, such as the function keys, we override the keyDown: method from the NSResponder class. In this method, we grab the first character from the passed NSEvent's characters property. We then compare this unichar to Cocoa's predefined NSFunctionKey's to handle the function key we are interested in. In our sample code, we display the special keys in our custom view, MyView, by using an NSAttributedString.

Lastly, we make a call to setNeedsDisplay:YES in order to redraw our view to show which special key was pressed.

There's more...

You can find a complete list of all the NSFunctionKey's in Apple's documentation under the NSEvent Class Reference. Look under the heading "Function-Key Unicodes".

Working with NSResponder

Understanding how the responder chain works is important when building your own views with event handling. In this recipe, you will learn about NSResponder's three most important methods and the sequence in which they are called.

Getting ready

In Xcode, create a new Cocoa Application and name it NSResponder.

How to do it...

  1. In the Xcode project, right-click on the Classes folder and choose Add…, then choose New File… Under the MacOS X section, select Cocoa Class, then select Objective-C class. Finally, choose NSView in the Subclass of popup. Name the new file MyView.m.
  2. In Xcode, Add the following code to the drawRect: method of the MyView class implementation:

    NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:
    [self bounds] xRadius:8.0 yRadius:8.0];
    [path setClip];

    [[NSColor whiteColor] setFill];
    [NSBezierPath fillRect:[self bounds]];

    [path setLineWidth:3.0];

    [[NSColor grayColor] setStroke];
    [path stroke];

  3. Double-click on the MainMenu.xib file in the Xcode project. From the Interface Builders Library palette, drag a TextField into the application window.
  4. In Interface Builder, drag a Custom View into the application window below the text field you just added.
  5. Using the Inspector palette, select the Identity tab and select MyView from the Class popup.
  6. Right-click on Window (NSResponder) and connect initialFirstResponder to the text field you dragged into the application window in step 3.
  7. Next, right-click on the TextField and connect the nextKeyView to the MyView Custom View:

    Cocoa and Objective-C: Handling Events

  8. Right-click on the MyView Custom View and connect its nextKeyView to the TextField:

    Cocoa and Objective-C: Handling Events

  9. Choose Save from the File menu to save the changes that you have made:

    Cocoa and Objective-C: Handling Events

  10. Back in Xcode, open the MyView.m file and add the following three methods to the MyView class implementation:

    - (BOOL)acceptsFirstResponder {
    NSLog(@"acceptsFirstResponder");
    return YES;
    }

    - (BOOL)becomeFirstResponder {
    NSLog(@"becomeFirstResponder");
    return YES;
    }

    - (BOOL)resignFirstResponder {
    NSLog(@"resignFirstResponder");
    return YES;
    }

  11. Choose Build and Run from Xcode's toolbar to run the application.

How it works...

As we have done in the other recipes in this article, we start by creating a custom NSView called MyView. Our custom view overrides three methods from the NSResponder class to print the method names to the console when they are called.

When you run the code in this recipe, use the Tab key to move between the NSTextField and custom view. This will change the first responder and you will see the sequence of methods called the custom view becomes and resigns its first responder status.

There's more...

The three methods we have overridden from NSResponder, acceptsFirstResponder, becomeFirstResponder , and resignFirstReponder all return a BOOL value indicating whether or not our subclass has handled the first responder status. In this recipe, we have returned YES in all three methods. However, in some cases you may not want to resign the first responder status because the user has not completed entering some kind data. In this case, you would return NO until the user has entered the correct data.

Since this recipe's focus was NSResponder, you might like to know that NSResponder also defines the method presentError:, which when called with an NSError object will display an NSAlert. Calling this method will send the error up the responder chain. If no other responders handle the error, then NSApp will display the error message.

Application-wide notifications with NotificationCenter

Using the NSNotificationCenter, you can send and receive notifications through your application using a simple publish and subscribe-like mechanism. Cocoa also makes use of notifications which you can observe to detect various changes. For example, when a user resizes a window, the NSWindowDidEndLiveResizeNotification notification will be posted to the notification center. Any of your classes can observe this notification and perform some action when the resizing of the window is complete.

In this recipe, we will send a notification when the application has finished launching. The notification will tell our custom view to change its color from white to red when it receives the notification.

Getting ready

In Xcode, create a new Cocoa Application and name it Notification.

How to do it...

  1. In the XCode project, right-click on the Classes folder and choose Add…, then choose New File… Under the MacOS X section, select Cocoa Class, then select Objective-C class. Finally, choose NSView in the Subclass of popup. Name the new file MyView.m.
  2. In Xcode, click on the NotificationAppDelegate.m file and add the following code in the applicationDidFinishLaunching method:

    NSDictionary *dictionary = [NSDictionary
    dictionaryWithObject:[NSColor redColor] forKey:@"fillColor"];

    NSNotificationCenter *notificationCenter = [NSNotificationCenter
    defaultCenter];
    [notificationCenter postNotificationName:@"ColorChange"
    object:self userInfo:dictionary];

  3. Click on MyView.h to open it and add the color variable to the class interface: NSColor *color;
  4. Click on MyView.m to open it and change the initWithFrame: method so that it looks like this:

    - (id)initWithFrame:(NSRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
    color = [NSColor whiteColor];

    NSNotificationCenter *notificationCenter = [NSNotificationCenter
    defaultCenter];
    [notificationCenter addObserver:self
    selector:@selector(handleColorChange:) name:@"ColorChange"
    object:nil];
    }
    return self;
    }

  5. Add the following code in the drawRect: method of the MyView class implementation:

    NSBezierPath *path = [NSBezierPath bezierPathWithRect:[self
    bounds]];
    [color set];
    [path fill];

    [[NSColor grayColor] set];
    [path setLineWidth:3.0];
    [path stroke];

  6. Add the following method to the MyView class implementation:

    - (void) handleColorChange:(NSNotification *)notification {
    NSLog(@"Received notification");

    NSDictionary *dictionary = [notification userInfo];
    color = [dictionary objectForKey:@"fillColor"];

    [self setNeedsDisplay:YES];
    }

  7. Add the following dealloc method to the MyView class:

    - (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self];

    [super dealloc];
    }

  8. Double-click on MainMenu.xib to open the file in Interface Builder.
  9. Drag a Custom View from Interface Builder's Library palette into the application window.
  10. Using the Inspector palette, select the Identity tab and select MyView from the Class popup.
  11. Choose Save from the File menu to save the changes that you have made.
  12. Click on Build and Run on Xcode's toolbar to run the application.

How it works...

When the application has finished launching, it will send a ColorChange notification through the default notification center with the color red.

In our custom view, which is just a white filled rectangle with a gray border, we add ourselves as an observer in the default notification center observing the ColorChange notification. We also specify that the setColor: method should be called when we receive the notification.

In the setColor: method of our view, we pull the color out of the notification and set the color property of our view. We then call the setNeedsDisplay:YES method to force our view to redraw with the new color.

Finally, we remove our view from the notification center when our view is deallocated.

There's more...

Cocoa also provides an NSDistributedNotificationCenter class to pass notifications between applications. For more information see Apple's companion guide Notification Programming Topics.

Summary

This article introduced keyboard, mouse, and gesture events and interpreting these events in your applications.


Further resources related to this subject:


Cocoa and Objective-C Cookbook Move beyond basic Cocoa development using over 70 simple and effective recipes for Mac OS X development
Published: May 2011
eBook Price: $23.99
Book Price: $39.99
See more
Select your format and quantity:

About the Author :


Jeff Hawkins

Jeff Hawkins has been developing software solutions and applications for 19 years. He has worked for Adobe Systems supporting third-party developers writing plug-ins for FrameMaker on the Macintosh, Windows, and Solaris platforms. He also has worked for a startup delivering prime-time television shows via satellite to television stations across the United States. Jeff currently works in the Tools and Architecture group for ADP Inc. designing and coding solutions for enterprise payroll systems. Jeff has extensive experience working with C, C++, Objective-C, Java, and JavaScript. In his spare time, Jeff enjoys working with Apple's iOS developing mobile applications and games. Jeff is also a private pilot with a seaplane rating and has built and flown his own Van's RV-8 airplane.

Books From Packt


Core Data iOS Essentials
Core Data iOS Essentials

iPhone Applications Tune-Up: RAW
iPhone Applications Tune-Up: RAW

Cocos2d for iPhone 0.99 Beginner's Guide
Cocos2d for iPhone 0.99 Beginner's Guide

iPhone JavaScript Cookbook
iPhone JavaScript Cookbook

jQuery 1.4 Animation Techniques: Beginners Guide
jQuery 1.4 Animation Techniques: Beginners Guide

Drupal 7 Themes
Drupal 7 Themes

Blender 2.5 HOTSHOT
Blender 2.5 HOTSHOT

Python 2.6 Graphics Cookbook
Python 2.6 Graphics Cookbook


Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software