shouldAutorotateToInterfaceOrientation?

UPDATE – 2009-07-07: I’ve updated this post and the example project to support iPhone OS 3.0. The code is now compliant and work in all currently existing versions of iPhone OS, latest right now is iPhone OS 3.0

This post is something I’ve worked on for quite a while. One of the biggest challenges building HittaHem was the photo-mode. I wanted the images in full screen and of course I wanted the images to rotate as the iPhone was rotated. At first this seemed like a piece of cake, just return YES to the shouldAutorotateToInterfaceOrientation event! Sure, that does it, but I didn’t want the whole application to rotate because the rest of the UI didn’t look good that way.

borat_high_five

Instead of redesigning the entire application just to support rotating images I decided to solve the problem myself. I’ve seen other people ask how to accomplish this on other blogs etc so I know I’m not the only stupid person who thinks it’s a good idea to only rotate a part of the application. I did ask the Italian evangelist person at the iPhone Tech Talk World Tour if there really wasn’t an easier way to do it, his response was “why would you want to do that?”. I tried to explain but apparently it didn’t make sense to him. At least I knew I wasn’t a complete fool investing hours on rewriting something that could be done with one line of code. Facebook rotates the images in the albums without rotating the entire app, and Facebooks (as my mom calls it), can’t be idiots, right?

Instead of pasting tons of code here I’ll try to explain my solution and the biggest problems implementing it. As usual you’ll find a complete demo project at the end of the post.

The easiest way to know if the device is rotated is to listen to the shouldAutorotateToInterfaceOrientation event. But instead of returning YES and rotate the entire app, I forward the event to the superview before returning NO. The changes in iPhone OS 3.0 “break” shouldAutorotateToInterfaceOrientation. It should still return NO for everything except portrait mode. This is how we can listen to rotation events, works in both 3.0 and previous versions:

	// Listen to did rotate event
	[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
	[[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(receivedRotate:) name: UIDeviceOrientationDidChangeNotification object: nil];
 
// This method is called by NSNotificationCenter when the device is rotated.
-(void) receivedRotate: (NSNotification*) notification
{
	UIDeviceOrientation interfaceOrientation = [[UIDevice currentDevice] orientation];
 
	if(interfaceOrientation != UIDeviceOrientationUnknown)
		[self deviceInterfaceOrientationChanged:interfaceOrientation];
}

The superview, in this case the image gallery, can will decide how to rotate its content to fit the new orientation. I place all my images inside a UIScrollView using the PageControl code from Apple. So what happens when the device is rotated? This image is very important:

iPhone orientations

It shows origo (or 0,0) position on the screen in UIInterfaceOrientationLandscapeRight, UIInterfaceOrientationPortrait and UIInterfaceOrientationLandscapeLeft.

Portrait mode
The images are positioned next to each other following the x-axis. The width of the content in the UIScrollView equals the width of one image multiplied by the number of images. So if we have 6 images the contentSize of the UIScrollView is the same height as the screen and the width is 6 times the width of the screen. Height=480 Width=(320*6)=1920. The position of the leftmost image is 0,0.

UIInterfaceOrientationLandscapeRight
LandscapeRight is the “easy” rotation because the images are positioned almost like portrait mode, the difference being they are positioned over the y-axis instead of x. Again if we have 6 images the contentSize of the UIScrollView is the same height as the screen width and the width is 6 times the height of the screen. Height=320 Width=(480*6)=2880. The position of the leftmost image is 0,0.

UIInterfaceOrientationLandscapeLeft
This is where it gets a bit tricky. Because we still want the images positioned from left to right and the 0,0 point is now in the top right corner. If we were to position the images following the y-axis as in LandscapeRight the image positions would be reversed. The first image must be positioned at the highest y-axis value. Same example with 6 images: The leftmost image will be positioned at the screen width multiplied by 5, X=0 Y=5*480. The rightmost image is positioned at 0,0.

Here’s the code to calculate the correct position:

-(CGRect)calculateFramePosition:(CGRect)frame page:(int)page
{
	// Calculate frame position depending on the interface orientation
	if(currentOrientation == UIInterfaceOrientationLandscapeLeft)
	{
		frame.origin.x = 0;
		frame.origin.y = scrollView.contentSize.height - (frame.size.height * (page+1));
	}
	else if(currentOrientation == UIInterfaceOrientationLandscapeRight)
	{
		frame.origin.x = 0;
		frame.origin.y = frame.size.height * page;
	}
	else
	{
		frame.origin.x = frame.size.width * page;
		frame.origin.y = 0;
	}
	return frame;
}

The actual rotations are done by the UIView inside the UIScrollView. The view inside the scrollview just holds an UIImage that I’ve subclassed to support taps. The rotations are simple PI rotations using CGAffineTransformMakeRotation. When rotating from portrait to landscape the width/height of the frame must be flipped, otherwise you image won’t be resized correctly. Code:

-(void) rotatePortrait
{
	portraitImageView.transform = CGAffineTransformMakeRotation(0);
 
	CGRect frame = portraitImageView.frame;
	frame.origin.y = 0;
	frame.origin.x = 0;
	frame.size.width = portraitImageView.frame.size.height;
	frame.size.height = portraitImageView.frame.size.width;
	portraitImageView.frame = frame;
 
}
 
-(void) rotateLandscapeRight
{
 
	portraitImageView.transform = CGAffineTransformMakeRotation(M_PI/2);
 
	// imageOrientation is set to it's new value after the rotation. So right now
	// imageOrientation has the value of the orientation we are rotating from
	// The reason we are not doing anything here unless we are rotating from portrait
	// is that the width/height is flipped around. And we don't do that
	// if the device is already in landscape.
	if(imageOrientation == UIInterfaceOrientationPortrait)
	{
		CGRect frame = portraitImageView.frame;
		frame.origin.y = 0;
		frame.origin.x = 0;
		frame.size.width = portraitImageView.frame.size.height;
		frame.size.height = portraitImageView.frame.size.width;
		portraitImageView.frame = frame;
	}
 
}
 
-(void) rotateLandscapeLeft
{
	portraitImageView.transform = CGAffineTransformMakeRotation(-M_PI/2);
 
	// imageOrientation is set to it's new value after the rotation. So right now
	// imageOrientation has the value of the orientation we are rotating from
	// The reason we are not doing anything here unless we are rotating from portrait
	// is that the width/height is flipped around. And we don't do that
	// if the device is already in landscape.
	if(imageOrientation == UIInterfaceOrientationPortrait)
	{
		CGRect frame = portraitImageView.frame;
		frame.origin.y = 0;
		frame.origin.x = 0;
		frame.size.width = portraitImageView.frame.size.height;
		frame.size.height = portraitImageView.frame.size.width;
		portraitImageView.frame = frame;
	}
 
}

Here’s a video of the demo application. It’s using static images bundled with the app and can easily be changed to download images from the internet. Check out my post on downloading images asynchronously. I’ve done my best to add useful comments along with the code but if you have questions don’t hesitate to post a comment below. The code is free to use as you see fit, the only thing I ask is that you don’t publish it as your own and if you found it usefull: add a link to this blog from your site/blog.

Download example project – Updated for iPhone OS 3.0

48 thoughts on “shouldAutorotateToInterfaceOrientation?

  1. Hi, I am new to iphone and objective-c programming, I am very interesting in this application that add rotate to pageControl program, if you can post you codes to me, I want to study it, I can’t find codes from links. Could you send me?xianyu.ge@gmail.com

  2. Doh. My bad! I had uploaded the code with this post but forgotten to add the link to it. It’s there now :) , and thanks for pointing this out.

  3. hm.. Very nice tutorial.
    I’m using a xml file and parsing all my images into an array.
    well i can get the caption text for each image, but how do I do when i want each image to show in the scroll.

    sry maybe it’s a twisty question. But if someone would know, I would be very happy :]

    thank you anyhow!!

  4. Or just to make the question a little bit easier.

    If i change imageInGallery to 1 image.

    And the image i want to display is an URL from Internet.
    Thats what i really want to do. Load images URL from Internet.

    thank you again :D

  5. Hi rajn, if you want to download images from the internet you can do it synchronouslyor asynchronously. Asynchronously is the perfered method because it doesn’t lock application interaction while it’s downloading. You can find my example and code for downloading an image asynchronously here: http://blog.sallarp.com/iphone-uiimage-asynchronously/

    Hope it helps and good luck!

  6. Hey. That’s sounds very nice. Well, I want do use the this application where you can rotate the images. The thing is when i’ve parsed my xml. I don’t know how to loop the the array of images in the ScrollView. Because every image is an url from the internet. Do you think it can work with both this app build together?

    Guess I just have to try now, but if you now a easier and better way to do it, let me know :]

    Thank you for your help, really appreciate it!

  7. Yes, that will work. That’s what I do in my app. I don’t quite understand the problem. You do know how to loop through an array?

    For each image in your arrya (that you already parsed) you add a new null object in loadDataForView, just like the example. When the a page is visible in the scrollview the method loadScrollViewWithPage i called and that’s where you actually create the view for the image. In my example the image url is passed to that view (the page index is used to get the image url out of the array). You can do the same thing with your array, only difference is that you pass your internet url. You then implement the image download code in that view. You could also pass other arguments, such as image caption etc there if you want to display that aswell.

    So basically all you need to do is add the download code in the ImageGalleryItem class and you’re done.

    Let me know how it works out. Good luck!

    // Björn

  8. Ok. I’m a newbie @ this :]

    But i now first tried to download this app and get my parsed array like this:

    Image *aImage = [appDelegate.images objectAtIndex:page];

    NSString *imgUrl= [NSString stringWithFormat:@"http://mysite.com/images/%@", aImage.src];
    UIImage *img = [UIImage imageWithData: [NSData dataWithContentsOfURL:[NSURL URLWithString:imgUrl]]];

    ImageGalleryItem *controller = [viewControllers objectAtIndex:page];
    if ((NSNull *)controller == [NSNull null]) {
    controller = [[ImageGalleryItem alloc] initWithNibNameAndParameters:@”ImageGalleryItem” bundle:[NSBundle mainBundle] interfaceOrientation:currentOrientation
    imageUrl:img delegate:self];
    [viewControllers replaceObjectAtIndex:page withObject:controller];
    [controller release];
    }

    But then I get a warning:

    Warning: passing argument 4 of ‘initWithNibNameAndParameters:bundle:interfaceOrientation: imageUrl:delegate’ from distinct Objective-C type

  9. You’re downloading the image synchronously and then pass the image object to the ImageGalleryItem. Insterad you should pass the image url as a string and let the ImageGalleryItem load the image asynchronously.

  10. Ok oops, now i get it :]

    It works now, thank you very much for your time and help :D

    //rajn

  11. Hi!

    If you want to add a little space between the images when they scrolling. What do you write then?

  12. Just make the image smaller so it doens’t cover the entire ImageGalleryItem and you’ll get some space.

  13. Yeah i’ve tried that.
    But then the images is small all the time, i just want it to be small when you scrolling between images.
    Just like the photo.app if you understand what I mean :]

    Sorry, maybe will be awful lot of code. So you don’t have to post that. But if not, i’m here :]

  14. Hello Bjorn,

    Is there a way to get the effect similar to photos app in iphone.. When i click the back button in landscape mode the same effect as in here should happen with a slide in animation.

  15. Hi Muthu,

    Yes, it’s actually pretty easy to do. Have a look at Apples transitions demo. Moving between views are always done the same way.

    Good luck!

  16. Hey!! I used the breaking changes shouldAutorotateToInterfaceOrientation article for rotation in 3.0.

    I seems to working fine until I rotate during the scrolling, if i’m rotating then, the image just disappear and if I tapped the black background the first images is displaying, anything you could to about this ?

  17. I’ve updated this example to work with iPhone OS 3.0.

    Random, I tested scrolling and rotating at the same time in this new version. It works pretty well. The only bug/problem was if i scroll to the first picture and at the exact right time rotated the phone I would end up on the last picture instead of the first. Perhaps it can be solved but I don’t have time to fix that. If you find a way, please let me know and I will update the example.

    Cheers!

  18. Very nice, thanks!

    Just one question, I’m using an array to load different images from categories in the previous view. But every time I choose a new category and load new images in scrollView, the application will be more tough and lazy. I’ll hope you understand :]

    How and where should i release the scrollView every time i scroll or choose a new category? Or is there any other way to solve this?:]

  19. @nicEPrice
    If you’re moving back to the category selection from the ScrollView image gallery you should release the scroll view then. Use leaks to make sure you’re not having memory leaks in your app, that could cause the app to run slower. If you are not releasing the scroll view properly the slowdown could be because you get memory warnings and the leaked scroll view starts to release images/slides through the didReceiveMemoryWarning event.

    Leaks is an awesome tool for finding leaks and investigating memory usage. Here’s a good tutorial on how to use it: http://www.streamingcolour.com/blog/tutorials/tracking-iphone-memory-leaks/

  20. Thanks a lot for your how-to but I have a problem. If the iPhone is in UIInterfaceOrientationPortraitUpsideDown orientation when the gallery is started, after when I change iPhone orientation, pictures aren’t at the good size.
    Do you know how to solve this?

    Thanks

  21. Björn Sållarp,
    Thank you for posting this tutorial. I’m using this example for one of my application. I want to apply zooming. Can anyone help what to change in this code to allow zooming in Scrollview?
    thanks

  22. thank you so much! I spent ages trying to figure out how to rotate just one element, I started out like you, returning no for the shouldAutoRotate but using the orientation to do a transform. But then discovered that it wouldn’t fire when returning from landscape back to portrait… thank you for showing me the way to do it right.

  23. Thank you very much for making this code available – it’s really annoying that apple don’t make there picture viewer available, but this code works just as well!

    I have found one small bug in it however. In some unusal circumstances, the pictures can end up in a funny position. This wasn’t easy to recreate on a regular basis, but after some time spent spinning my phone round, standing on my head etc :o ) I managed to recreate the problem consistently as follows:

    1) Display a portrait picture (i.e a picture that fills the screen when the phone is held portrait).
    2) Then follow the next few steps all in one movement.
    3) Move the phone to a position so that the screen is pointing to the ceiling.
    4) Turn the phone 360 degrees in a way that after turning it 180 degrees the phone screen is pointing at the floor.
    5) Tilt the phone back to its starting position (portrait).

    The result of these steps is that the image is roughly a third of the size (probably the size it is displayed in landscape mode) and positioned at the top right corner of the screen.

    I have managed to fix the problem by changing the following line in the ImageGallery classes receivedRotate method:

    if (interfaceOrientation != UIDeviceOrientationUnknown)
    [self deviceInterfaceOrientationChanged:interfaceOrientation];

    to:

    if (interfaceOrientation != UIDeviceOrientationUnknown &&
    interfaceOrientation != UIDeviceOrientationFaceUp &&
    interfaceOrientation != UIDeviceOrientationFaceDown)
    [self deviceInterfaceOrientationChanged:interfaceOrientation];

    Hope this helps and thanks again for making the code available in the first place.

    Simon

  24. hi,
    i m really interested to know how to select another imageview to start. for example in the middle. please let me know.

    thanks

  25. Hey. Thanks for the great code!

    Total n00b question for you: how do I make the images selectable/save-able? I want to make a wallpaper gallery from some of my photos.

    Thanks again!

  26. cool work, thanks! one question: what about if i need UIInterfaceOrientationPortraitUpsideDown too? any hint? thanks in advance! best regards

  27. Hi, I can’t figure out where in your code is telling that the images resize when is landscape, for example in number 3 when is landscape it fits nicely, just in the center.

  28. Nice tutorial
    I download the project it works fine with os 4.1
    I have a question
    from where we can change the images in the code
    i deleted the images from the”images” file in resources and replace it with new images, but when i run the code, the same images is displayed again.
    can you help me please, because i need it for my senior project.
    Thanks

  29. @Sumayya,

    If you’re still experiencing this problem try cleaning before you build the project and also try to reset the simulator (iOS Simulator -> Reset content and settings…).

  30. Hi!

    I tried it on Xcode 4.1 and iOS 4.3.1 simulator. Unfortunately it didn’t work well. When I “click” the on the first image, it returns to the first screen.

    Anyone noticed the same problem?

    Regards,
    Pablo Cantero

  31. Thanks for this example. I am currently trying to built a web gallery and this has helped alot. I am still filtering through all the code (I basicly have no idea what I am doing). Thanks for the tutorial.

  32. The image automatically changed when i changed the orientation where status bar at left hand side, if i not scrolled initially then empty screen appear, if i scrolled displaying the last image in gallery.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>