UIScrollView and Multi-Touch zooming

Let's say you have a nice, big, hi-res photo you want to show to your user on the iPhone. No doubt you want to let your user zoom in to see details of the photo and scroll around, just like the built-in Photos.app.
Most of you would probably come up with something akin to the following structure using Interface Builder:

UIView

|
–UIScrollView
|
–UIImageView
If you are coding the UI by hand, the top level UIView would probably be omitted. This may or may not be relevant to the issue you'll be encountering. See my note at the end.
Now that the view structure is setup, you will probably discover that double tapping to zoom in/out is not implemented out of the box by Apple (surprise!).
"Ok", you thought to yourself, "I just need to implement the touch events in the controller and it will all work."
So you go ahead and added touch event code to the controller class, probably something like this:

– (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {

UITouch *touch = [touches anyObject];

NSInteger tapCount = [touch tapCount];

if (tapCount == 2) {

[scrollView_ setZoomScale:zoomScale animated:YES];

}

}

When you run the code, you'll find that the touchesEnded method never get called!
Turns out this is actually by design from Apple. touchesEnded and other touch event methods are no-op methods by default. My guess is that this is probably a performance related design decision.
Regardless of why, here is what you need to do to 'fix' this. First, create a new class that inherits from UIScrollView. Then in this new subclass, implements the touch event method that you want to use and passes on the event to the next responder in the list. Like this:

– (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event

{

[super touchesEnded:touches withEvent:event];

[self.nextResponder touchesEnded:touches withEvent:event];

}

This will pass on the touch event to the parent UIView in the hierarchy and thus calls the touch method in the controller.
Problem solved!
(One thing I haven't tried is to removed the top level UIView from the hierarchy, and link the UIScrollView to the view outlet in the controller. My hunch is that this may eliminate the need to subclass UIScrollView.)

Read and post comments

|

Send to a friend

Advertisements

4 comments

  1. hatfinch

    Oops, not sure where the second half of my post above got to. I was going to say, 3.0 has a different behaviour to <=2.2.1: as mentioned in the release notes UIScrollView's hitTest:withEvent: method now returns the proper subview rather than itself, and seemingly in support of this, the way that touches are cancelled is now handled completely differently. (Make a test project with a simple UIScrollView subclass (with a contentSize larger than its frame.size) that prints log messages from the touches*:withEvent: methods, with and without calling super, and see how its behaviour differs between 2.2.1 and 3.0.)

  2. Alex

    Interesting. I haven't been targetting 2.2.x so I didn't notice the difference in behavior. I don't have any issue with multi-touches gesture, though I am not trying to ignore UIScrollView's zoomScale property like you are.

  3. hatfinch

    The problem with UIScrollView is that it seems to have been designed in two separate halves: the scrolling mechanism being designed to allow for a content area greater than the maximum size of a view (hence contentSize rather than contentView) but the zooming mechanism assuming a single viewForZoomingInScrollView.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s