Code, Code, Revolution!
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.

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:
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
With this blog I try to provide useful tips and solutions for programming .NET, Objective-C and more. My name is Björn Sållarp, and I love writing code.
xianyu.ge
March 14th, 2009 at 12:42 am
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
Björn Sållarp
March 14th, 2009 at 2:18 am
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.
xnanoob
April 14th, 2009 at 7:39 am
i spend a lot of time for it.
thank you very much for this post.
it very nice.
Find the device orientation using the accelerometer | blog.sallarp.com
May 7th, 2009 at 9:57 pm
[...] likely we’ll see more changes at all. In February I published a quite lengthy solution on how to rotate images without rotating the entire application where I use shouldAutorotateToInterfaceOrientation. Recently I wrote a post about the changes [...]
rajn
May 8th, 2009 at 9:05 am
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!!
rajn
May 8th, 2009 at 2:31 pm
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
Björn Sållarp
May 8th, 2009 at 6:19 pm
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!
rajn
May 11th, 2009 at 9:04 am
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!
Björn Sållarp
May 11th, 2009 at 10:15 am
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
rajn
May 11th, 2009 at 11:38 am
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
Björn Sållarp
May 11th, 2009 at 2:13 pm
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.
rajn
May 11th, 2009 at 4:36 pm
Ok oops, now i get it :]
It works now, thank you very much for your time and help
//rajn
Sanjay
May 13th, 2009 at 9:07 am
Hi!
If you want to add a little space between the images when they scrolling. What do you write then?
Björn Sållarp
May 13th, 2009 at 10:35 am
Just make the image smaller so it doens’t cover the entire ImageGalleryItem and you’ll get some space.
Sanjay
May 13th, 2009 at 1:11 pm
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 :]
Björn Sållarp
May 13th, 2009 at 7:54 pm
I understand what you’re after. Check this page on StackOverflow: http://stackoverflow.com/questions/849383/photos-app-like-gap-between-pages-in-uiscrollview-with-pagingenabled
Hope it helps!
Muthu Chidambaram
May 20th, 2009 at 11:24 am
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.
iPhone OS 3.0 - Breaking changes to shouldAutorotateToInterfaceOrientation | blog.sallarp.com
May 21st, 2009 at 11:06 am
[...] shouldAutorotateToInterfaceOrientation which I use to rotate images in an UIScrollView. I’ve explained and posted that code. Since beta 1 of iPhone OS 3.0 shouldAutorotateToInterfaceOrientation doesn’t behave like in [...]
Björn Sållarp
May 21st, 2009 at 1:24 pm
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!
Michel
June 5th, 2009 at 2:51 pm
That’s a really cool post, thanks!
djlinus
July 7th, 2009 at 8:38 am
I you have the time, could you post the a link for working rotation in 3.0 to?
djlinus
July 7th, 2009 at 8:39 am
with link i mean a sample project :]
random
July 7th, 2009 at 9:31 am
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 ?
Björn Sållarp
July 7th, 2009 at 10:35 pm
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!
nicEPrice
July 23rd, 2009 at 2:30 pm
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?:]
Björn Sållarp
July 25th, 2009 at 10:27 am
@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/
Brian Egge
August 11th, 2009 at 11:04 am
Thanks heaps for the example. It was a huge help for fixing a bug in my project.
iPatx
August 17th, 2009 at 4:19 pm
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
Imran
October 14th, 2009 at 12:40 pm
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
Chris
October 14th, 2009 at 11:45 pm
Grazie mille
ben
December 9th, 2009 at 11:07 am
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.
Simon
December 18th, 2009 at 5:20 pm
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
) 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
omar
January 24th, 2010 at 4:22 pm
hi,
i m really interested to know how to select another imageview to start. for example in the middle. please let me know.
thanks
omar
January 25th, 2010 at 11:20 am
hi,
is it possible put in loop the scrollview??? how to do this??
thanks
Matt
February 25th, 2010 at 10:12 pm
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!
ray
April 7th, 2010 at 11:25 pm
cool work, thanks! one question: what about if i need UIInterfaceOrientationPortraitUpsideDown too? any hint? thanks in advance! best regards
Dustin
May 17th, 2010 at 10:28 pm
Fantastic, thanks.