Friday, October 3, 2008

A Bit About the Responder Chain

Yesterday's post about handling double-taps provoked a question about how to handle double-taps using a UIWebView. I haven't worked much with UIWebView, but I think a little explanation of the way the responder chain works on the iPhone may help; it's a little different than the responder chain on Mac OS X.

Controllers are now officially part of the responder chain. Any event goes first to the First Responder, as you might expect.

If the First Responder (which is usually the UI element being interacted with) doesn't consume the event, it is not always passed directly up to the superview, as it is on OS X. Rather the First Responder checks to see if it has a controller, and if there is one, it does a lateral pass over to it. This gives the controller class the opportunity to respond to and/or consume events before it is passed up to the superview. This is why you can implement methods like touchesBegan:withEvent: on either a view or a view controller.

This gives you two options if you need to intercept events normally handled by a UIKit object, like the UIView: You can implement the touchesBegan:withEvent: either on the object's controller, or you can create a subclass of the object and implement the method there.

I don't know which should be used with UIWebView because I don't know if UIView consumes the double tap event. I'm guessing it does (I'll update if I get confirmation of this), so my guess is that you would have to subclass UIWebView to intercept double-taps. If it doesn't consume the event, then you could just intercept it in the controller class for the web view.

Whoah - made a little mistake in the original posting. When implementing this code in a controller class, you want to forward to the next responder. When subclassing an existing view that handles touches, you want to pass to super! I've corrected the post below to reflect the difference


Here's the trick, though. Implementing a method like touchesBegan:withEvent, by default, consumes that event so that it goes no further through the Responder Chain. However, if you're intercepting an event for a control that detects touches or gestures, then you need to make sure that you pass the event down the responder chain manually if you don't handle it completely.

Here is how you might intercept a double-tap in your controller class, letting any events that are not handled by your method proceed down th responder chain:

-(void)respondToFictionalEvent:(UIEvent *)event {
if (someCondition)
[self handleEvent:event];
else
[self.nextResponder respondToFictionalEvent:event];
}


When subclassing an existing view than has touch-handling code, you have to handle it a little differently. What you have to do instead, is to pass the unhandled event up to super, like so:


-(void)respondToFictionalEvent:(UIEvent *)event {
if (someCondition)
[self handleEvent:event];
else
[super respondToFictionalEvent:event];
}


Basically, if you don't want to consume the event, you have to manually call the same method on the next responder. That's it. That's the magic that lets you intercept some, but not all, events for an existing object. So, to intercept a double-tap in a subclass of UIWebView, you might do something like this:

Header: MyUIViewSubclass.h

#import <UIKit/UIKit.h>


@interface MyUIViewSubclass : UIView {

}

@end


Implementation: MyUIViewSubclass.m

#import "MyUIViewSubclass.h"


@implementation MyUIViewSubclass

- (void)touchesBegan:(NSSet *)touches
withEvent:(UIEvent *)event {


UITouch *touch = [touches anyObject];
NSUInteger tapCount = [touch tapCount];
BOOL consumed = NO;

if (tapCount == 2) {
// Do whatever...
consumed = YES;
}



if (!consumed)
[super touchesBegan:touches withEvent:event];
}

@end


Note - this is not tested code, but it should give you an idea of how the process works. This is the basic process you would use any time you need to intercept some, but not all, responder chain events.

No comments:

Post a Comment