Event Handling

The last chapter showed you how to create a class that displays a message box with two buttons. However, the message box lacked a critical feature: there was no way to notice when a user clicked on one of its buttons, and on which button.

In Sparrow, this is handled through a flexible event system. If you navigate back to the image displaying the class hierarchy of display objects, you will notice that SPDisplayObject inherits from a class called SPEventDispatcher. Thus, each and every display object is already prepared to handle events. In Sparrow, the event system is tightly coupled with the display tree. This has some advantages we will see later.

The easiest way to understand the event system is by looking at an example. The following code creates a button and logs out a message when it was pressed.

- (id)init
{
    if (self = [super init])
    {
        SPTexture *buttonTexture = [SPTexture textureWithContentsOfFile:@"button.png"];
        mButton = [SPButton buttonWithUpState:buttonTexture];
        [mButton addEventListener:@selector(onButtonTriggered:) atObject:self
                          forType:SP_EVENT_TYPE_TRIGGERED];
        [self addChild:mButton];
    }
    return self;
}
 
- (void)onButtonTriggered:(SPEvent *)event
{
    NSLog(@"The button was triggered!");
}

After creating the button, you use the method 'addEventListener:atObject:forType' to register an event listener on that button. The button inherits this method from SPEventDispatcher. The arguments have the following meaning:

  • addEventListener: the method that is called when the event is fired. That's always a method with one argument of type SPEvent (or a subtype).
  • atObject: the object on which the above method should be invoked.
  • forType: an event type is always identified by a string. In this case, we want to listen for all “triggered”-events.

It's a good practice to remove that event listener when it's no longer needed. You can do so e.g. in the dealloc method. (The event dispatcher does not retain the listener, though, so you won't create a memory leak if you don't remove it.)

- (void)dealloc
{
    [mButton removeEventListenersAtObject:self forType:SP_EVENT_TYPE_TRIGGERED];
    [super dealloc];
}

There are already several useful events predefined in Sparrow:

  • SP_EVENT_TYPE_TRIGGERED: a button was triggered
  • SP_EVENT_TYPE_ADDED: a display object was added to a container
  • SP_EVENT_TYPE_ADDED_TO_STAGE: a display object was added to a container that is connected to the stage
  • SP_EVENT_TYPE_REMOVED: a display object was removed from a container
  • SP_EVENT_TYPE_REMOVED_FROM_STAGE: a display object lost its connection to the stage
  • SP_EVENT_TYPE_ENTER_FRAME: some time has passed, a new frame is rendered (shown in the animation section)
  • SP_EVENT_TYPE_TOUCH: a touch event occurred
  • SP_EVENT_TYPE_TWEEN_STARTED / UPDATED / COMPLETED: a tween changed its state (shown in the animation section)

But you are not limited to those events. It's easy to create your own.

Creating custom events

The message box we created above should dispatch a custom event when the user pushes one of its buttons. There are two ways to do this. One is to fire a different event type for each button, like this.

First, define names for the events (MsgBox.h):

#define EVENT_TYPE_YES_TRIGGERED @"yesTriggered"
#define EVENT_TYPE_NO_TRIGGERED  @"noTriggered"

The event listeners of the buttons can now dispatch those events (MsgBox.m):

// the listener we created for the yes-button:
- (void)onYesButtonTriggered:(SPEvent *)event
{
    SPEvent *localEvent = [SPEvent eventWithType:EVENT_TYPE_YES_TRIGGERED];
    [self dispatchEvent:localEvent];
}
 
// the listener we created for the no-button:
- (void)onNoButtonTriggered:(SPEvent *)event
{
    SPEvent *localEvent = [SPEvent eventWithType:EVENT_TYPE_NO_TRIGGERED];
    [self dispatchEvent:localEvent];
}

Anybody who uses the message box can now listen for those events:

- (void)init
{
    if (self = [super init])
    {
        // ...
        MsgBox msgBox = [[MsgBox alloc] initWithText:@"Really exit?"];
        [msgBox addEventListener:@selector(onMsgBoxYesTriggered:) atObject:self
                         forType:EVENT_TYPE_YES_TRIGGERED];
        [msgBox addEventListener:@selector(onMsgBoxNoTriggered:) atObject:self
                         forType:EVENT_TYPE_NO_TRIGGERED];
        [self addChild:msgBox];
        [msgBox release];
    }
    return self;
}

The other way is to always fire the same event, but include information about which button was pressed. This is a common procedure; you will frequently need to pass some information with an event.

This is done by creating a subclass of SPEvent:

MsgBoxClosedEvent.h:

#define EVENT_TYPE_MSG_BOX_CLOSED @"msgBoxClosed"
 
@interface MsgBoxClosedEvent : SPEvent
{
    BOOL mResult;
}
 
- (id)initWithType:(NSString *)type result:(BOOL)result bubbles:(BOOL)bubbles;
 
@property (nonatomic, readonly) BOOL result;
 
@end

MsgBoxClosedEvent.m:

@implementation MsgBoxClosedEvent
 
@synthesize result = mResult;
 
- (id)initWithType:(NSString *)type result:(BOOL)result
{
    if (self = [super initWithType:type bubbles:NO])
    {
        mResult = result;
    }
    return self;
}
 
@end

The event listeners within MsgBox.m would now dispatch this custom event:

MsgBoxClosedEvent *event = [[MsgBoxClosedEvent alloc]
    initWithType:EVENT_TYPE_MSG_BOX_CLOSED result:YES];
[self dispatchEvent:event];
[event release];

Whoever created the MsgBox would now have to listen only to one event type: EVENT_TYPE_MSG_BOX_CLOSED. Which button was pressed is passed via the custom event type.

- (void)onMsgBoxClosed:(MsgBoxClosedEvent *)event
{
    NSLog(@"MsgBox was closed. Result: %d", event.result);
}

Bubbling events

In our previous examples, the event dispatcher and the event listener were directly connected via the “addEventListener”-method. But sometimes, that's not what you want.

Let's say you created a complex game with a deep display tree. Somewhere in the branches of this tree, a penguin sprite just collided with a boost container. Now, this information is needed at the root of the display tree, in the game's main class (the stage). It would be really cumbersome to hand this event down from the penguin over numerous display objects until it reaches the stage.

That's why events support something that is called “bubbling”. Imagine a real tree (it's your display tree) and turn it around by 180 degrees, so that the trunk points upwards. The trunk, that's your stage, and the leaves of the tree are you display objects. Now, if a leaf creates a bubbling event, it will go upwards just like the bubbles in a glass of soda, traveling from branch to branch (from parent to parent) until it finally reaches the trunk.

Any display object along this route can listen to this event. It can even pop the bubble and stop it from traveling further.

All that is required to do that is to set the “bubbles”-property of an event to YES.

SPEvent *bubblingEvent = [[SPEvent alloc] initWithType:SOME_EVENT_TYPE bubbles:YES];
[self dispatchEvent:bubblingEvent];
[bubblingEvent release];

Anywhere along its path, you can listen to this event:

[self addEventListener:@selector(onBubblingEvent:) atObject:self forType:SOME_EVENT_TYPE];

Notice that we are listening to events from “self” – even though the event was dispatched by some other object. We could add a listener to any object along the path of the bubbling event.

This feature comes in handy in numerous situations. Especially concerning touch events.

Touch events

One of the most revolutionary features of the iPhone is that it allows you to control it with your fingers. No physical buttons, not even a stylus is required – and you can even use several fingers simultaneously. Sparrow makes it easy for you to react to touches of one or more fingers.

First of all: if you need support for multitouch, make sure that your instance of SPView (the Cocoa canvas Sparrow draws into) has “multipleTouchEnabled” set to YES. Otherwise, Cocoa won't inform Sparrow about more than one touch at a time.

Now, to react to touch events, just add the following event listener to your class:

[self addEventListener:@selector(onTouch:) atObject:self forType:SP_EVENT_TYPE_TOUCH];

Again, we added the listener to 'self', without ever dispatching a touch event ourself. That's a strong indication that touch events are bubbling events – and indeed, that's the case. If you think about it, that makes perfect sense:

Remember our message box. When the user clicks on the text field, obviously anybody listening to touches on the text field must be notified. But the same is true for somebody listening for touch events on the message box – the textfield is part of the message box, so the latter was touched as well. And if somebody listens to touch events on the stage, he will also be notified. The message box is part of the stage, after all.

Now, how to handle touch events? Have a look at this sample code.

- (void)onTouch:(SPTouchEvent*)event
{
    SPTouch *touch = [[event touchesWithTarget:self andPhase:SPTouchPhaseBegan] anyObject];
    if (touch)
    {
        SPPoint *touchPosition = [touch locationInSpace:self];
        NSLog(@"Touched position (%f, %f)",
            touchPosition.x, touchPosition.y);
    }
}

That's the most basic case: Find out if somebody touched the screen, and log the coordinates (relative to the current display object). The method “touchesWithTarget:andPhase:” is provided by SPTouchEvent and helps you find the touches you are interested in.

The target “self” means: find any touches that occurred on me (self) OR my children. Think back to our message box: the target “self” would include the message box and the text field.

The touch phase is “touchPhaseBegan”, thus the position will be logged the moment the finger touched the screen, but not while the finger moves around or leaves the screen. Those are the available touch phases. Their names should be self explanatory.

  • SPTouchPhaseBegan
  • SPTouchPhaseMoved
  • SPTouchPhaseStationary
  • SPTouchPhaseEnded

The method “touchesWithTarget:andPhase:” returns an NSSet containing suitable touches, which are encapsulated in SPTouch objects. The method “locationInSpace:” of SPTouch returns the touch coordinates in just the coordinate system you need.

So much for single touches. Multiple touches are handled just the same. The only difference is that the set returned by “touchesWithTarget:andPhase” contains multiple SPTouch objects.

NSArray *touches = [[event touchesWithTarget:self andPhase:SPTouchPhaseMoved] allObjects];
 
if (touches.count == 1)
{
    // one finger touching
    SPTouch *touch = [touches objectAtIndex:0];
    SPPoint *currentPos = [touch locationInSpace:self];
    SPPoint *previousPos = [touch previousLocationInSpace:self];
    // ...
}
else if (touches.count >= 2)
{
    // at least two fingers touching
    SPTouch *touch1 = [touches objectAtIndex:0];
    SPTouch *touch2 = [touches objectAtIndex:1];
    // ...
}

Notice the method “previousLocationInSpace:” of SPTouch. In this sample, we requested touches with the phase SPTouchPhaseMoved. “previousLocationInSpace” will return the point the touch had in previous frame. This comes in handy when you want to drag an object around: just move the object along the vector between the current and the last touch position.

The demo application contains the class “TouchSheet”, which is used in the “Multitouch” demo scene. It shows you how to let the user drag objects around, rotate and scale them.


Next section: Displaying Text

  manual_v1/event_handling.txt · Last modified: 2013/03/05 10:19 by 127.0.0.1
 
Powered by DokuWiki