IOS Autorotation Hell: Bad or wrong view position offsets after rotation

Edit: This is solved, answer at the end of this post.

The Problem

It looks like autorotation hell is back again.

I’m trying to move some views around during a device rotation with strange results.

I have created a simple hello world single view application. In this application I have two views: the view controller’s view, and a square subview in the top left corner.

The general idea here is that we want the main view to be full screen, and the little square view to always remain in the top left corner, but for some reason autorotation to landscape makes offsets go wonky for the main view.

When the app rotates from portrait to landscape, the main view has an odd offset, but the subview is still in the 0,0 corner of main view. As these images show:

Original portrait orientation:

Landscape after rotation 1:

Portrait after same-direction rotation 2 (fine):

Landscape after same-direction rotation 3:

If I continue to rotate in the same direction, the view stays in these same positions for each orientation mode (it doesn’t get worse as more 360 rotations continue).

Here’s my App delegate, I did not change anything here:

  1. (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
  2. {
  3.     self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
  4.     // Override point for customization after application launch.
  5.     if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
  6.         self.viewController = [[ViewController alloc] initWithNibName:@"ViewController_iPhone" bundle:nil];
  7.     } else {
  8.         self.viewController = [[ViewController alloc] initWithNibName:@"ViewController_iPad" bundle:nil];
  9.     }
  10.     self.window.rootViewController = self.viewController;
  11.     [self.window makeKeyAndVisible];
  12.     return YES;
  13. }

And my view controller’s code:

  1. @interface ViewController ()
  2. @property (strong) UIView * secondView;
  3. @end
  4.  
  5. @implementation ViewController
  6.  
  7. (void) doRotation:(UIInterfaceOrientation)toInterfaceOrientation {
  8.     BOOL orientationIsLandscape = UIInterfaceOrientationIsLandscape(toInterfaceOrientation);
  9.     CGRect screenRect = [[UIScreen mainScreen] bounds];
  10.     screenRect = CGRectMake(0, 0, screenRect.size.width, screenRect.size.height); //anchor to 0,0
  11.     if (orientationIsLandscape) {
  12.         NSLog(@"orientation is landscape..");
  13.         //swap width and height..
  14.         screenRect = CGRectMake(0, 0, screenRect.size.height, screenRect.size.width);
  15.     }
  16.     [self.secondView removeFromSuperview];
  17.     self.secondView = [[UIView alloc] initWithFrame:CGRectMake(0,0,100,100)];
  18.     self.secondView.backgroundColor = [UIColor greenColor];
  19.     [self.view addSubview:self.secondView];
  20.     self.view.backgroundColor = [UIColor blueColor];
  21.     self.view.frame = screenRect;
  22.     self.view.bounds = screenRect;
  23. }
  24.  
  25. (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
  26.     return YES;
  27. }
  28.  
  29. (void) willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
  30.     [super willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration:duration];
  31.     [self doRotation:toInterfaceOrientation];
  32. }
  33.  
  34. (void)viewWillAppear:(BOOL)animated{
  35.     [self doRotation:self.interfaceOrientation];
  36. }
  37.  
  38. @end

I have turned auto-layout off on viewcontroller’s XIB, and this project is in IOS 5 SDK to keep things simple (and not bring in IOS 6 auto rotation madness into the mix). The application is set to target IOS version of 5.1.

Stack Overflow thread: http://stackoverflow.com/questions/13236755/ios-auto-rotation-leaves-views-with-odd-offset

The Answer

After poking through stackoverflow threads with the ‘autorotate’ tag, I found the answer to my problems: I’m not setting the transform property on my views that I want to rotate.

This stack overflow thread has the answer to my problem:

http://stackoverflow.com/questions/8777199/how-to-rotate-sub-views-around-their-own-centres

The key is, if you’re trying to simply rotate a view, there’s a transform property on views that you can use to do the rotation in place, rather than mess with coordinate bits that mess things up as shown earlier.

Here’s the fixed viewcontroller code, with IOS 5 and 6 compatability:

  1. @interface ViewController ()
  2. @property (strong) UIView * secondView;
  3. @end
  4.  
  5. @implementation ViewController
  6.  
  7. (void) viewDidLoad {
  8.     [super viewDidLoad];
  9.     self.view.backgroundColor = [UIColor blueColor];
  10.     self.secondView = [[UIView alloc] initWithFrame:CGRectMake(0,0,100,100)];
  11.     self.secondView.backgroundColor = [UIColor greenColor];
  12.     [self.view addSubview:self.secondView];
  13. }
  14.  
  15. (void) doRotation:(UIInterfaceOrientation)orientation duration:(NSTimeInterval)duration {
  16.     CGFloat rotationAngle = 0;
  17.     if (orientation == UIDeviceOrientationPortraitUpsideDown) rotationAngle = M_PI;
  18.     else if (orientation == UIDeviceOrientationLandscapeLeft) rotationAngle = M_PI_2;
  19.     else if (orientation == UIDeviceOrientationLandscapeRight) rotationAngle = M_PI_2;
  20.     [UIView animateWithDuration:duration animations:^{
  21.         self.view.transform = CGAffineTransformMakeRotation(rotationAngle);
  22.         self.secondView.transform = CGAffineTransformMakeRotation(rotationAngle);
  23.     } completion:nil];
  24. }
  25.  
  26. /* ios 6 rotation methods */
  27. (NSUInteger)supportedInterfaceOrientations {
  28.     return UIInterfaceOrientationMaskAll;
  29. }
  30.  
  31. (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
  32.     [super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
  33.     [self doRotation:toInterfaceOrientation duration:duration];
  34. }
  35.  
  36. /* ios 5 rotation methods */
  37. (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
  38.     return YES;
  39. }
  40.  
  41. (void) willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
  42.     [super willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration:duration];
  43.     [self doRotation:toInterfaceOrientation duration:duration];
  44. }
  45.  
  46. (void)viewWillAppear:(BOOL)animated{
  47.     [self doRotation:self.interfaceOrientation duration:0.1f];
  48. }
  49. @end

Alternatively, if you can, playing with a view’s autoresizingMask property will do exactly what you expect it to do in most cases. Make sure you set these in your NIB files, using this thing:

If you’re doing autoresizingMask by hand in code, there’s two dimensions of resizing: the view’s height/width, or the placement of the view. Here are some examples

  1. //make this view center on the screen always (assuming you start it in a centered position), maintaining size
  2. //that is, tell IOS that this view's margins between itself and parent can grow/shrink on all sides
  3. view.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleRightMargin;
  4.  
  5. //make this view stick to top left position it's in, maintaining size
  6. //that is, tell IOS that this view is not flexible in position top/left wise, but can change right/bottom
  7. view.autoresizingMask = UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleRightMargin;
  8.  
  9. //make this view stay where it is, adjusting height/width size
  10. view.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
  11.  
  12. //prevent this view from autoresizing at all
  13. view.autoresizingMask = UIViewAutoResizingNone;
  14.  
  15. //another way to prevent autoresize for all children of a view
  16. view.autoresizesSubviews = NO;

Related Resources