Example: The Icon Shuffle

This example creates four icons on the screen and allows the user to move them around freely, either individually or two at a time using a gesture. This illustrates the use of various geometry structures and functions, event notifications, and Graphics Services functions. To pick up an icon, tap and hold it, move it where it should go, then release your finger. If the status bar is tapped, the icons are reset to their original positions.

To compile this example from the command line, you'll need to use several different frameworks: Core Graphics, Graphics Services, and UIKit, in addition to the foundation frameworks:

$ arm-apple-darwin-gcc -o MyExample MyExample.m -lobjc \
    -framework CoreFoundation -framework Foundation \
    -framework UIKit -framework CoreGraphics \
    -framework GraphicsServices

Example 4-1 and Example 4-2 contain the header file and executable methods for the example.

Example 4-1. Mouse and gesture example (MyExample.h)

#import <CoreFoundation/CoreFoundation.h>
#import <UIKit/UIKit.h>
#import <UIKit/UITextView.h>

@interface MainView : UIView
{
    UIImage *images[4];
    CGRect positions[4];
    CGPoint offsets[4];
    int dragLeft, dragRight;
}
- (id)initWithFrame:(struct CGRect)windowRect;
- (void)reInit;
- (void)mouseDown: (struct _  _GSEvent *)event;
- (void)mouseUp: (struct _  _GSEvent *)event;
- (void)mouseDragged: (struct _  _GSEvent *)event;
- (void)gestureStarted: (struct _  _GSEvent *)event;
- (void)gestureEnded: (struct _  _GSEvent *)event;
- (void)gestureChanged: (struct _  _GSEvent *)event;
- (void)drawRect:(CGRect)rect;
@end

@interface MyApp : UIApplication
{
    UIWindow *window;
    MainView *mainView;
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification;
- (void)statusBarMouseDown:(struct _  _GSEvent *)event;
@end

Example 4-2. Mouse and gesture example (MyExample.m)

#import <Foundation/Foundation.h>
#import <CoreFoundation/CoreFoundation.h>
#import <GraphicsServices/GraphicsServices.h>
#import "MyExample.h"

int main(int argc, char **argv)
{
    NSAutoreleasePool *autoreleasePool = [
        [ NSAutoreleasePool alloc ] init
    ];
    int returnCode = UIApplicationMain(argc, argv, [ MyApp class ]);
    [ autoreleasePool release ];
    return returnCode;
}

@implementation MyApp
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    window = [ [ UIWindow alloc ] initWithContentRect:
        [ UIHardware fullScreenApplicationContentRect ]
    ];

    CGRect rect = [ UIHardware fullScreenApplicationContentRect ];
    rect.origin.x = rect.origin.y = 0.0f;

    mainView = [ [ MainView alloc ] initWithFrame: rect ];
    [ window setContentView: mainView ];
    [ window orderFront: self ];
    [ window makeKey: self ];
    [ window _setHidden: NO ];

}

- (void)statusBarMouseDown:(struct _  _GSEvent *)event {
    [ mainView reInit ];
    [ mainView setNeedsDisplay ];
}

@end

@implementation MainView
- (id)initWithFrame:(struct CGRect)windowRect {
    if ((self == [ super initWithFrame: windowRect ]) != nil) {
        int i;

        images[0] = [ UIImage
          imageAtPath: @"/Applications/MobilePhone.app/icon.png" ];
        images[1] = [ UIImage
          imageAtPath: @"/Applications/MobileMail.app/icon.png" ];
        images[2] = [ UIImage
          imageAtPath: @"/Applications/MobileSafari.app/icon.png" ];
        images[3] = [ UIImage
            imageAtPath:
            @"/Applications/MobileMusicPlayer.app/icon.png" ];

        [ self reInit ];
    }
    return self;
}

- (void)reInit {
        positions[0] = CGRectMake(98, 178, 60, 60);
        positions[1] = CGRectMake(162, 178, 60, 60);
        positions[2] = CGRectMake(98, 242, 60, 60);
        positions[3] = CGRectMake(162, 242, 60, 60);

        dragLeft = dragRight = −1;
}

- (void)drawRect:(CGRect)rect {
    float black[4] = { 0, 0, 0, 1 };
    CGContextRef ctx = UICurrentContext(  );
    int i;

    CGContextSetFillColor(ctx, black);
    CGContextFillRect(ctx, rect);

    for(i=0;i<4;i++) {
        [ images[i] draw1PartImageInRect: positions[i] ];
    }
}

- (void)mouseDown: (struct _  _GSEvent *)event {
    CGPoint point = GSEventGetLocationInWindow(event);
    int i;

    for(i=0;i<4;i++) {
        if (CGRectContainsPoint(positions[i], point)) {
            dragLeft = i;
            offsets[i] = CGPointMake
              ( point.x - positions[i].origin.x,
                point.y - positions[i].origin.y );
        }
    }
}

- (void)mouseUp: (struct _  _GSEvent *)event {
    CGPoint point = GSEventGetLocationInWindow(event);
    int i;

    dragLeft = −1;
}

- (void)mouseDragged: (struct _  _GSEvent *)event {
    CGPoint point = GSEventGetLocationInWindow(event);
    CGRect old;
    int i;

    if (dragLeft != −1) {
        old = positions[dragLeft];
        positions[dragLeft].origin.x = point.x - offsets[dragLeft].x;
        positions[dragLeft].origin.y = point.y - offsets[dragLeft].y;
        [ self setNeedsDisplayInRect: old ];
        [ self setNeedsDisplayInRect: positions[dragLeft] ];
    }
}

- (void)gestureStarted: (struct _  _GSEvent *)event {
    CGPoint leftFinger = GSEventGetInnerMostPathPosition(event);
    CGPoint rightFinger = GSEventGetOuterMostPathPosition(event);
    int i;

    for(i=0;i<4;i++) {
        if (CGRectContainsPoint(positions[i], leftFinger)) {
            dragLeft = i;
            offsets[i] = CGPointMake
              ( leftFinger.x - positions[i].origin.x,
                leftFinger.y - positions[i].origin.y );
        }
        else if (CGRectContainsPoint(positions[i], rightFinger)) {
            dragRight = i;
            offsets[i] = CGPointMake
              ( rightFinger.x - positions[i].origin.x,
                rightFinger.y - positions[i].origin.y );
        }
    }
}

- (void)gestureEnded: (struct _  _GSEvent *)event {
    CGPoint leftFinger = GSEventGetInnerMostPathPosition(event);
    CGPoint rightFinger = GSEventGetOuterMostPathPosition(event);
    int i;

    dragLeft = dragRight = −1;

    for(i=0;i<4;i++) {
        if (CGRectContainsPoint(positions[i], leftFinger))
            dragLeft = i;
        else if (CGRectContainsPoint(positions[i], rightFinger))
            dragRight = i;
    }
}

- (void)gestureChanged: (struct _  _GSEvent *)event {
    CGPoint leftFinger = GSEventGetInnerMostPathPosition(event);
    CGPoint rightFinger = GSEventGetOuterMostPathPosition(event);
    CGRect old;
    int i;

    if (dragLeft != −1) {
        old = positions[dragLeft];
        positions[dragLeft].origin.x
            = leftFinger.x - offsets[dragLeft].x;
        positions[dragLeft].origin.y
            = leftFinger.y - offsets[dragLeft].y;
        [ self setNeedsDisplayInRect: old ];
        [ self setNeedsDisplayInRect: positions[dragLeft] ];
    }

    if (dragRight != −1) {
        old = positions[dragRight];
        positions[dragRight].origin.x
            = rightFinger.x - offsets[dragRight].x;
        positions[dragRight].origin.y
            = rightFinger.y - offsets[dragRight].y;
        [ self setNeedsDisplayInRect: old ];
        [ self setNeedsDisplayInRect: positions[dragRight] ];
    }
}

- (BOOL)canHandleGestures {

   return YES;
}

@end

What's Going On

Here's how the icon shuffle works:

  1. When the application instantiates, a MainView object is created, which is derived from UIView, and its initWithFrame method is called. This initializes the images and positions of four icons on the screen: Phone, Mail, Safari, and iPod. The view class is then told to display itself.

  2. As the view class is drawn, the class's drawRect method is called. This causes a black rectangle to first be rendered to blank the screen's background. Next, each icon is individually rendered on the screen using methods from the UIImage class (discussed more in Chapter 7).

  3. When a single finger is used to move an icon, the mouseDown method is first called. This checks to see which icon the user has pressed and sets it in the object's dragLeft variable as the actively moving icon. The delta between where the user pressed inside the icon and the upper-left corner (origin) of the icon is stored so that the example can track the exact part of the icon that was pressed with the user's finger.

  4. When the user's finger moves, the mouseDragged method is called, which sets the icon position to the current finger position, adjusting for where the user actually pressed inside the icon. This way, if the user pressed the center of the icon, the icon is moved so that its center will track with the user's finger. The setNeedsDisplayInRect method is called to invoke the view class's drawRect method again.

  5. When the user's finger lifts up, the mouseUp method is called, which resets the active icon.

  6. If two fingers are used, the gesture methods perform the same tasks, but process both finger positions, allowing two icons to track with the user's fingers. Because you don't know whether the fingers will be put down and raised in the same order, separate information must be stored on the left and right fingers. Note that putting down a second finger causes the information saved by the mouseDown method to be thrown away and replaced by the information returned by the the gestureStarted method. Similarly, raising the second finger causes the information saved by the gestureChanged method to be thrown away and replaced by the information returned by the mouseDraqgged method.

Further Study

It's a good idea to explore all of the different methods supported in these lower-level classes. Check out the following prototypes in your tool chain's include directory. These can be found in /usr/local/arm-apple-darwin/include: UIKit/UIResponder.h, UIKit/UIView.h, GraphicsServices/GraphicsServices.h, and CoreGraphics/CGGeometry.h.

Get iPhone Open Application Development now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.