====== Event Handling ======
[[Display Objects|Previously]], I 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.
This can be handled with the help of Sparrow's flexible event system. If you have another look at 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.
{{:manual:class_hierarchy_v1_1.png?760|All display objects inherit from SPEventDispatcher}}
===== Standard Events =====
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.
@implementation Game
{
SPButton *_button;
}
- (id)init
{
if (self = [super init])
{
// create a button
SPTexture *buttonTexture = [SPTexture textureWithContentsOfFile:@"button.png"];
_button = [SPButton buttonWithUpState:buttonTexture];
[self addChild:_button];
// here's the interesting part:
[_button addEventListener:@selector(onButtonTriggered:) atObject:self
forType:SP_EVENT_TYPE_TRIGGERED];
}
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, because that's what buttons dispatch when they are pressed.
If you try that out now, you'll see a new log line in the debug console each time you press the button.
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
{
[_button removeEventListenersAtObject:self forType:SP_EVENT_TYPE_TRIGGERED];
}
There are already several useful events predefined in Sparrow:
* **SP_EVENT_TYPE_TRIGGERED:** a button was triggered
* **SP_EVENT_TYPE_TOUCH:** a touch event occurred (more on this in the next section)
* **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 (more on this in the animation section)
* **SP_EVENT_TYPE_COMPLETED:** a sound or movie clip finished playback
But you are not limited to those events. It's easy to create your own.
===== Custom events =====
Let's return to the message box we introduced earlier. It should be displayed to ask the user if she wants to exit the game. There are two buttons on that box, one indicating "Yes", the other indicating "No". The box should dispatch a custom event when the user pushes one of these buttons.
{{ :manual:msgbox-yes-no.png?nolink&400 |}}
There are two ways to do this. One is to fire a different event type for each button. Let's look at how that can be done!
=== (A) Dispatch a different event type for each button ===
First, define the event types we're going to use:
#define EVENT_TYPE_YES_TRIGGERED @"yesTriggered"
#define EVENT_TYPE_NO_TRIGGERED @"noTriggered"
@interface MsgBox : SPSprite
@end
Then, within your message box class, you listen for the "TRIGGERED" events on both buttons. In the relevant event handlers, you create a new event --- using a different event type for each button.
- (id)init
{
if ((self = [super init]))
{
// create our two buttons
SPButton *yesButton = ...;
SPButton *noButton = ...;
// add them to the display tree
[self addChild:yesButton];
[self addChild:noButton];
// listen for their TRIGGERED events
[yesButton addEventListener:@selector(onYesTriggered:) atObject:self
forType:SP_EVENT_TYPE_TRIGGERED];
[noButton addEventListener:@selector(onNoTriggered:) atObject:self
forType:SP_EVENT_TYPE_TRIGGERED];
}
return self;
}
// the listener we created for the yes-button:
- (void)onYesTriggered:(SPEvent *)event
{
SPEvent *localEvent = [SPEvent eventWithType:EVENT_TYPE_YES_TRIGGERED];
[self dispatchEvent:localEvent];
}
// the listener we created for the no-button:
- (void)onNoTriggered:(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];
}
return self;
}
- (void)onMsgBoxYesTriggered:
{
NSLog(@"User chose 'yes'");
}
- (void)onMsgBoxNoTriggered:
{
NSLog(@"User chose 'no'");
}
=== (B) Dispatch a custom Event ===
The other way is to always fire the same event type, 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:
#define EVENT_TYPE_MSG_BOX_CLOSED @"msgBoxClosed"
@interface MsgBoxClosedEvent : SPEvent
- (id)initWithType:(NSString *)type result:(BOOL)result;
@property (nonatomic, readonly) BOOL result;
@end
@implementation MsgBoxClosedEvent
{
BOOL _result;
}
@synthesize result = _result;
- (id)initWithType:(NSString *)type result:(BOOL)result
{
if ((self = [super initWithType:type bubbles:NO]))
{
_result = result;
}
return self;
}
@end
The event listeners within MsgBox.m can now dispatch this custom event, using a different "result" value depending on the button that was triggered.
- (void)onYesTriggered:
{
MsgBoxClosedEvent *event = [[MsgBoxClosedEvent alloc]
initWithType:EVENT_TYPE_MSG_BOX_CLOSED result:YES];
[self dispatchEvent:event];
}
- (void)onNoTriggered:
{
MsgBoxClosedEvent *event = [[MsgBoxClosedEvent alloc]
initWithType:EVENT_TYPE_MSG_BOX_CLOSED result:NO];
[self dispatchEvent:event];
}
Whoever created the MsgBox will now have to listen to only 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 your 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.
{{ :manual:bubbling-event.png?nolink&170|An event bubbling from D to A}}
Here is a simple (albeit somewhat artificial) example: we've got 4 sprites that make up a simple hierarchy. When the leaf-sprite (d) dispatches a bubbling event, any of the other sprites can listen to that event.
SPSprite *a = [SPSprite sprite];
SPSprite *b = [SPSprite sprite];
SPSprite *c = [SPSprite sprite];
SPSprite *d = [SPSprite sprite];
[a addChild:b];
[b addChild:c];
[c addChild:d];
[a addEventListener:... forType:@"click"];
[d dispatchEvent:[SPEvent eventWithType:@"click" bubbles:YES]];
Note that while we dispatched the event on "d", we added the event listener to object "a"! In fact, we could have added it to any of the objects, because the event will "travel" to each of them.
All that is required to do that is to set the "bubbles"-property of an event to YES. Listening to the event works just like before --- with the difference that you can add the listener to any object along the parent chain of the object.
SPEvent *bubblingEvent = [[SPEvent alloc] initWithType:EVENT_TYPE bubbles:YES];
[self dispatchEvent:bubblingEvent];
This feature comes in handy in numerous situations, especially concerning [[Touch Events]]. But before we look at those, let me show you some other shortcuts that simplify event handling.
-----
//Next section: [[Enhanced Events]]//