UIScrollView with centered UIImageView, like Photos app

39,290

Solution 1

This code should work on most versions of iOS (and has been tested to work on 3.1 upwards).

It's based on the Apple WWDC code mentioned in Jonah's answer.

Add the below to your subclass of UIScrollView, and replace tileContainerView with the view containing your image or tiles:

- (void)layoutSubviews {
    [super layoutSubviews];

    // center the image as it becomes smaller than the size of the screen
    CGSize boundsSize = self.bounds.size;
    CGRect frameToCenter = tileContainerView.frame;

    // center horizontally
    if (frameToCenter.size.width < boundsSize.width)
        frameToCenter.origin.x = (boundsSize.width - frameToCenter.size.width) / 2;
    else
        frameToCenter.origin.x = 0;

    // center vertically
    if (frameToCenter.size.height < boundsSize.height)
        frameToCenter.origin.y = (boundsSize.height - frameToCenter.size.height) / 2;
    else
        frameToCenter.origin.y = 0;

    tileContainerView.frame = frameToCenter;
}

Solution 2

Here is what I'd consider, the solution as in it behaves exactly like apple's photo app. I had been using solutions that used:

-(void) scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale

to recenter but I didn't like that solution because after the zooming was done, it'd bounce then quick 'jump' into the center which was very un-sexy. Turns out if you pretty much do the exact same logic but in this delegate function:

-(void)scrollViewDidZoom:(UIScrollView *)pScrollView

it both starts off centered and when you zoom out it stays centered:

-(void)scrollViewDidZoom:(UIScrollView *)pScrollView {
CGRect innerFrame = imageView.frame;
CGRect scrollerBounds = pScrollView.bounds;

if ( ( innerFrame.size.width < scrollerBounds.size.width ) || ( innerFrame.size.height < scrollerBounds.size.height ) )
{
    CGFloat tempx = imageView.center.x - ( scrollerBounds.size.width / 2 );
    CGFloat tempy = imageView.center.y - ( scrollerBounds.size.height / 2 );
    CGPoint myScrollViewOffset = CGPointMake( tempx, tempy);

    pScrollView.contentOffset = myScrollViewOffset;

}

UIEdgeInsets anEdgeInset = { 0, 0, 0, 0};
if ( scrollerBounds.size.width > innerFrame.size.width )
{
    anEdgeInset.left = (scrollerBounds.size.width - innerFrame.size.width) / 2;
    anEdgeInset.right = -anEdgeInset.left;  // I don't know why this needs to be negative, but that's what works
}
if ( scrollerBounds.size.height > innerFrame.size.height )
{
    anEdgeInset.top = (scrollerBounds.size.height - innerFrame.size.height) / 2;
    anEdgeInset.bottom = -anEdgeInset.top;  // I don't know why this needs to be negative, but that's what works
}
pScrollView.contentInset = anEdgeInset;
}

Where 'imageView' is the UIImageView you're using.

Solution 3

Apple has released the 2010 WWDC session videos to all members of the iphone developer program. One of the topics discussed is how they created the photos app!!! They build a very similar app step by step and have made all the code available for free.

It does not use private api either. I can't put any of the code here because of the non disclosure agreement, but here is a link to the sample code download. You will probably need to login to gain access.

http://connect.apple.com/cgi-bin/WebObjects/MemberSite.woa/wa/getSoftware?code=y&source=x&bundleID=20645

And, here is a link to the iTunes WWDC page:

http://insideapple.apple.com/redir/cbx-cgi.do?v=2&la=en&lc=&a=kGSol9sgPHP%2BtlWtLp%2BEP%2FnxnZarjWJglPBZRHd3oDbACudP51JNGS8KlsFgxZto9X%2BTsnqSbeUSWX0doe%2Fzv%2FN5XV55%2FomsyfRgFBysOnIVggO%2Fn2p%2BiweDK%2F%2FmsIXj

Solution 4

I suspect that you need to set the UIScrollView's contentOffset.

Solution 5

I wish it was that simple. I did some research on the net and found that it is not just my problem, but many people are struggling with the same issue not just on iPhone, but on Apple's desktop Cocoa as well. See following links:

http://www.iphonedevsdk.com/forum/iphone-sdk-development/5740-uiimageview-uiscrollview.html
The described solution is based on the property UIViewContentModeScaleAspectFit of the image, but unfortunately it does not work very well .The image is centered and grows properly, but the bouncing area seems to be much bigger than the picture.

This guy did not get the answer either:
http://discussions.apple.com/thread.jspa?messageID=8322675

And finally, the same problem on Apple's desktop Cocoa:
http://www.cocoadev.com/index.pl?CenteringInsideNSScrollView
I suppose the solution works, but it is based on the NSClipView, which is not on iPhone...

Anybody has some solution working on iPhone?

Share:
39,290
Admin
Author by

Admin

Updated on May 05, 2020

Comments

  • Admin
    Admin about 4 years

    I would like to have scroll view with an image content view. The image is actually map which is much bigger than the screen. The map should be initially in the center of the scroll view, like photos in Photos app when you turn iPhone to landscape orientation.

    alt text

    I did not manage to have the map in the center with correct zooming and scrolling at the same time. Provided that the map image starts from the top of the screen (in portrait orientation), the code looks something like:

    - (void)loadView {
        mapView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"map.jpg"]];
        CGFloat mapHeight = MAP_HEIGHT * SCREEN_WIDTH / MAP_WIDTH;
        mapView.frame = CGRectMake(0, 0, SCREEN_WIDTH, mapHeight);
        scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT)];
        scrollView.delegate = self;
        scrollView.contentSize = mapView.frame.size;
        scrollView.maximumZoomScale = MAP_WIDTH / SCREEN_WIDTH;
        scrollView.minimumZoomScale = 1;
        [scrollView addSubview:mapView];
        self.view = scrollView;
    }
    

    When I move the image frame to the center, the image grows only from the top of its frame down. I tried to play around with mapView transform, with dynamically changing frame of the imageView. Nothing works for me so far.