views:

5657

answers:

3

I have a UIImageView that is displaying an image that is wider and taller than the UIImageView is. I would like to pan the image within the view using an animation (so that the pan is nice and smooth).

It seems to me that I should be able to just adjust the bounds.origin of the UIImageView, and the image should move (because the image should paint inside the view with that as its origin, right?) but that doesn't seem to work. The bounds.origin changes, but the image draws in the same location.

What almost works is to change the contentsRect of the view's layer. But this begins as a unit square, even though the viewable area of the image is not the whole image. So I'm not sure how I would detect that the far edge of the image is being pulled into the viewable area (which I need to avoid, since it displays by stretching the edge out to infinity, which looks, well, sub-par).

My view currently has its contentsGravity set to kCAGravityTopLeft via Interface Builder, if that makes a difference (Is it causing the image to move?). No other options seemed to be any better, though.

UPDATE: to be clear, I want to move the image inside the view, while keeping the view in the same spot.

+1  A: 

Here's a very good tutorial which explains exactly this

http://www.iphonesdkarticles.com/2008/09/multi-touch-tutorial-part-4.html

lostInTransit
My question needs to be clearer, apparently.This code doesn't really pan the image inside the view; it moves the view. While this looks like the same thing if the view fills the entire screen (as it does in the code you point to) it doesn't work if the UIImageView has siblings.
TALlama
+7  A: 

I'd highly recommend enclosing your UIImageView in a UIScrollView. Have the UIImageView display the full image, and set the contentSize on the UIScrollView to be the same as your UIImageView's size. Your "window" into the image will be the size of the UIScrollView, and by using scrollRectToVisible:animated: you can pan to particular areas on the image in an animated fashion.

If you don't want scroll bars to appear, you can set the showsHorizontalScrollIndicator and showsVerticalScrollIndicator properties to NO.

UIScrollView also provides pinch-zooming functionality, which may or may not be useful to you.

Brad Larson
But the animation in that case is so fast that you never actually see the image, just the destination.
TALlama
Enclose your scrollRectToVisible:animated: method call in a UIView beginAnimations/commitAnimations block and use UIView's setAnimationDuration: at the start of that block. This overrides the default animation duration. You can make the scroll view pan as slowly as you want that way.
Brad Larson
That doesn't seem to slow down the scrolling. Here's what I'm doing: [UIView beginAnimations:@"bay pan" context:nil]; [UIView setAnimationDuration:10]; [UIView setAnimationDelegate:self]; [scrollView scrollRectToVisible:newRect animated:YES]; [UIView commitAnimations];
TALlama
This lead me down the right path, so I'm giving you the "Accepted" to give you the reputation, and then posting my own answer to provide the code.
TALlama
+6  A: 

Brad Larson pointed me down the right road with his suggestion to put the UIImageView inside a UIScrollView.

In the end I put the UIImageView inside of a UIScrollView, and set the scrollView's contentSize and the imageView's bounds to be the same size as the image in the UIImage:

UIImage* image = imageView.image;
imageView.bounds = CGRectMake(0, 0, image.size.width, image.size.height);
scrollView.contentSize = image.size;

Then, I can animate the scrollView's contentOffset to achieve a nice panning effect:

[UIView beginAnimations:@"pan" context:nil];
[UIView setAnimationDuration:animationDuration];
scrollView.contentOffset = newRect.origin;
[UIView commitAnimations];

In my particular case, I'm panning to a random space in the image. In order to find a proper rect to pan to and a proper duration to get a nice constant speed, I use the following:

UIImage* image = imageView.image;

float xNewOrigin = [TCBRandom randomIntLessThan:image.size.width - scrollView.bounds.size.width];
float yNewOrigin = [TCBRandom randomIntLessThan:image.size.height - scrollView.bounds.size.height];

CGRect oldRect = scrollView.bounds;
CGRect newRect = CGRectMake(
    xNewOrigin,
    yNewOrigin, 
    scrollView.bounds.size.width,
    scrollView.bounds.size.height);

float xDistance = fabs(xNewOrigin - oldRect.origin.x);
float yDistance = fabs(yNewOrigin - oldRect.origin.y);
float hDistance = sqrtf(powf(xDistance, 2) + powf(yDistance, 2));
float hDistanceInPixels = hDistance;

float animationDuration = hDistanceInPixels / speedInPixelsPerSecond;

I'm using a speedInPixelsPerSecond of 10.0f, but other applications might want to use a different value.

TALlama