Showing posts with label Gestures. Show all posts
Showing posts with label Gestures. Show all posts

Sunday, December 6, 2009

A Better Two-Finger Rotate Gesture

A while back, I posted a mostly functional, but imperfect sample code for doing a two-finger rotate gesture. I've been meaning to revisit this for some time to get it working correctly.

Tonight, I finally found some time to do so, so I present a better, fully-functional two-finger rotate gesture sample code project. This version allows you to rotate 360° or more without problems. Rather than relying on the order of the touches and the overall rotation angle, I just calculate the angle between the fingers' current location and their previous location.

Screen shot 2009-12-06 at 10.05.04 PM.png
The new version is actually quite a bit simpler than the previous one. Since each instance of UITouch contains both its current location and its previous location, we don't even need to keep track of anything. The old version, in addition to not working correctly past 180° was working much harder than it needed to. The only new thing in this version is that the function that calculates the angle between the two lines looks at the slope of both lines and returns negative or positive values based on which slope is larger.

Monday, October 20, 2008

Simulator

I thought everybody knew this by now, but I have encountered two people recently who have been developing for the iPhone for a while and who didn't know this, so here it is. Sorry if I'm being Captain Obvious.

If you want to simulate a two-finger gesture in the iPhone simulator, hold down the option key. You will get two dots on the screen instead of one. The two dots will default to pinching - if you bring the dot closer to the center of the screen, the other dot comes toward the center, making it easy to simulate a pinch in or pinch out.

If you want to do a different two-finger gesture, get the two dots the distance apart that you want them to be, then hold down the shift key, while still holding down the option key. That will lock the position of the two finger presses together so you can do, for example, a two-finger swipe.

Still don't know a way to do three-or-more finger gestures, or complex two-finger gestures, but the use of the shift and option key should cover about 95% of the gestures you'd need.

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.