Zooming by Tapping
While the basic UIScrollView
class supports the pinch-in and pinch-out gestures with a very small amount of code, supporting a more rich zooming experience using tap detection requires more work on the part of your application.
The iOS Human Interface Guidelines defines a double-tap to zoom in and zoom out. That, however, assumes some specific constraints: that the view has a single level of zoom, such as in the Photos application, or that successive double-taps will zoom to the maximum amount and, once reached the next double-tap zooms back to the full-screen view. But some applications require a more flexible behavior when dealing with tap-to-zoom functionality, an example of this is the Maps application. Maps supports double-tap to zoom in, with additional double-taps zooming in further. To zoom out in successive amounts, Maps uses a two-finger touch, with the fingers close together, to zoom out in stages. While this gesture is not defined in the iOS Human Interface Guidelines, applications may choose to adopt it to mimic the Maps application, when the functionality is required.
In order for your application to support tap to zoom functionality, you do not need to subclass the UIScrollView
class. Instead you implement the required touch handling in the class for which the UIScrollView
delegate method viewForZoomingInScrollView:
returns. That class will be responsible for tracking the number of fingers on the screen and the tap count. When it detects a single tap, a double tap, or a two-finger touch, it will respond accordingly. In the case of the double tap and two-finger touch, it should programmatically zoom the scroll view by the appropriate factor.
Implementing the Touch-Handling Code
Supporting the tap, double tap, and two-finger tap in the touch code of a subclass of UIView
(or a descendent) requires implementing three methods: touchesBegan:withEvent:
, touchesEnded:withEvent:
, and touchesCanceled:withEvent:
. In addition, initialization of interaction, multiple touches, and tracking variables may be required. The following code fragments are from the TapToZoom example in the ScrollViewSuite sample code project, in the TapDetectingImageView
class, which is a subclass of UIImageView
.
Initialization
The gestures that are desired for the tap-to-zoom implementation require that user interaction and multiple touches are enabled for the view, and the methods to enable that functionality are called from the initWithImage:
method. This method also initializes two instance variables that are used to track state in the touch methods. The twoFingerTapIsPossible
property is a Boolean that is YES
unless more than two fingers are in contact with the device screen. The multipleTouches
property has a value of NO
unless their are more than one touch events detected. A third property, tapLocation
, is a CGPoint that is used to track the location of a double tap or the midpoint between the two fingers when a double touch is detected. This point is then used as the center point for zooming in or out using the programmatic zooming methods described in Zooming Programmatically.
- (id)initWithImage:(UIImage *)image { |
self = [super initWithImage:image]; |
if (self) { |
[self setUserInteractionEnabled:YES]; |
[self setMultipleTouchEnabled:YES]; |
twoFingerTapIsPossible = YES; |
multipleTouches = NO; |
} |
return self; |
} |
Once the initialization has occurred the class is ready when it receives touch events.
The touchesBegan:withEvent: Implementation
The touchesBegan:withEvent:
method first cancels any outstanding attempts to initiate handling a single finger tap, the handleSingleTap
message. The message is canceled because, if it has been sent, it is invalid as this is an additional touch event, ruling out a single tap. If the message has not been sent because this is the first touch, canceling the perform is ignored.
The method then updates the state of the tracking variables. If more than a single touch event has been received, then multipleTouches
property is set to YES
, because this may be a two finger touch. If more than two touch events have occurred, then the twoFingerTapIsPossible
property is set to NO
, touches by more than two fingers at a time are a gesture that is ignored.
The code for this method is as follows:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event |
{ |
// Cancel any pending handleSingleTap messages. |
[NSObject cancelPreviousPerformRequestsWithTarget:self |
selector:@selector(handleSingleTap) |
object:nil]; |
// Update the touch state. |
if ([[event touchesForView:self] count] > 1) |
multipleTouches = YES; |
if ([[event touchesForView:self] count] > 2) |
twoFingerTapIsPossible = NO; |
} |
The touchesEnded:withEvent: Implementation
This method is the workhorse of the tap handling and is somewhat complex. However, the code is well documented and is simply shown below. The midPointBetweenPoints
function is used to determine the point which a double touch will be centered upon when the handleTwoFingerTap
method is called, which results in the view zooming out a level.
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event |
{ |
BOOL allTouchesEnded = ([touches count] == [[event touchesForView:self] count]); |
// first check for plain single/double tap, which is only possible if we haven't seen multiple touches |
if (!multipleTouches) { |
UITouch *touch = [touches anyObject]; |
tapLocation = [touch locationInView:self]; |
if ([touch tapCount] == 1) { |
[self performSelector:@selector(handleSingleTap) |
withObject:nil |
afterDelay:DOUBLE_TAP_DELAY]; |
} else if([touch tapCount] == 2) { |
[self handleDoubleTap]; |
} |
} |
// Check for a 2-finger tap if there have been multiple touches |
// and haven't that situation has not been ruled out |
else if (multipleTouches && twoFingerTapIsPossible) { |
// case 1: this is the end of both touches at once |
if ([touches count] == 2 && allTouchesEnded) { |
int i = 0; |
int tapCounts[2]; |
CGPoint tapLocations[2]; |
for (UITouch *touch in touches) { |
tapCounts[i] = [touch tapCount]; |
tapLocations[i] = [touch locationInView:self]; |
i++; |
} |
if (tapCounts[0] == 1 && tapCounts[1] == 1) { |
// it's a two-finger tap if they're both single taps |
tapLocation = midpointBetweenPoints(tapLocations[0], |
tapLocations[1]); |
[self handleTwoFingerTap]; |
} |
} |
// Case 2: this is the end of one touch, and the other hasn't ended yet |
else if ([touches count] == 1 && !allTouchesEnded) { |
UITouch *touch = [touches anyObject]; |
if ([touch tapCount] == 1) { |
// If touch is a single tap, store its location |
// so it can be averaged with the second touch location |
tapLocation = [touch locationInView:self]; |
} else { |
twoFingerTapIsPossible = NO; |
} |
} |
// Case 3: this is the end of the second of the two touches |
else if ([touches count] == 1 && allTouchesEnded) { |
UITouch *touch = [touches anyObject]; |
if ([touch tapCount] == 1) { |
// if the last touch up is a single tap, this was a 2-finger tap |
tapLocation = midpointBetweenPoints(tapLocation, |
[touch locationInView:self]); |
[self handleTwoFingerTap]; |
} |
} |
} |
// if all touches are up, reset touch monitoring state |
if (allTouchesEnded) { |
twoFingerTapIsPossible = YES; |
multipleTouches = NO; |
} |
} |
The touchesCancelled:withEvent: Implementation
The view receives a touchesCancelled:withEvent:
message if the scroll view detects that the tap handling is no longer relevant because the finger has moved, which causes a scroll to begin. This method simply resets the state variables.
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { |
twoFingerTapIsPossible = YES; |
multipleTouches = NO; |
} |
The ScrollView Suite Example
The ScrollViewSuite sample code project has excellent examples of implementing zooming using tap gestures. The TapToZoom
example in the suite implements a subclass of UIImageView
that supports zooming using the tapping behavior as displayed in the Maps application. The implementation is generic enough, through it’s use of a delegate (typically the controller that manages the scroll view) to implement the actual handling of a tap, double-tap, or double touch, that you should be able to easily adapt the code, and design, to your own views.
The TapDetectingImageView
class is the subclass of UIImageView
that implements the touch handling, using the RootViewController
class as the delegate that handles the actual tap and touch responses, as well as the controller that initially configures the UIScrollView
.
Copyright © 2011 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2011-06-06