13

When my application launches, an NSLog of my view's bounds shows the following:

NSLog(@"My view frame: %@", NSStringFromCGRect(self.view.bounds));
My view frame: {{0, 0}, {320, 548}}

I then rotate the simulator and get the following:

NSLog(@"My view frame: %@", NSStringFromCGRect(self.view.bounds));
My view frame: {{0, 0}, {320, 548}}

Then when I rotate the simulator back to portait, I get this:

NSLog(@"My view frame: %@", NSStringFromCGRect(self.view.bounds));
My view frame: {{0, 0}, {568, 300}}

The two methods I'm using to adjust elements are these, they aren't done yet, as I'm currently in the middle of fixing them for the new iphone.

-(void) resizeForLandscape
{
    newsLabel = (UILabel *)[self.view viewWithTag:50];
    weatherLabel = (UILabel *)[self.view viewWithTag:51];
    sportsLabel = (UILabel *)[self.view viewWithTag:52];
    entertainmentLabel = (UILabel *)[self.view viewWithTag:53];
    videosLabel = (UILabel *)[self.view viewWithTag:54];
    moreLabel = (UILabel *)[self.view viewWithTag:55];

    NSLog(@"resizeForLandscape called");
    webView.frame = CGRectMake(0, 31, self.view.frame.size.width, self.view.frame.size.height - 75);    
    button.frame = CGRectMake(0, 0, image.size.width -30, image.size.height -15);
    myLabel.frame = CGRectMake(230, 100, 80, 21);
    label.frame = CGRectMake(0, 0, self.view.bounds.size.height + 15, 30);

    NSLog(@"My view frame: %@", NSStringFromCGRect(self.view.bounds));
    NSLog(@"my status bar height is %f", [UIApplication sharedApplication].statusBarFrame.size.height);
    NSLog(@"my status bar width is %f", [UIApplication sharedApplication].statusBarFrame.size.width);


    newsLabel.frame = CGRectMake((self.view.bounds.size.height - 346) / 2 + 12, self.view.bounds.size.width - 38, 43, 21);
    weatherLabel.frame = CGRectMake(newsLabel.frame.origin.x + 64, self.view.bounds.size.width - 38, 42, 21);
    sportsLabel.frame = CGRectMake(newsLabel.frame.origin.x + 126, self.view.bounds.size.width - 38, 42, 21);
    entertainmentLabel.frame = CGRectMake(newsLabel.frame.origin.x + 185, self.view.bounds.size.width - 38, 66, 21);
    videosLabel.frame = CGRectMake(newsLabel.frame.origin.x + 269, self.view.bounds.size.width - 38, 42, 21);
    moreLabel.frame = CGRectMake(newsLabel.frame.origin.x + 325, self.view.bounds.size.width - 38, 42, 21);

    spacer1.width = (self.view.bounds.size.height - 346) / 2;
    spacer2.width = 40;
    spacer3.width = 28;
    spacer4.width = 42;
    spacer5.width = 35;
    spacer6.width = 24;
}

-(void) resizeForPortrait
{
    newsLabel = (UILabel *)[self.view viewWithTag:50];
    weatherLabel = (UILabel *)[self.view viewWithTag:51];
    sportsLabel = (UILabel *)[self.view viewWithTag:52];
    entertainmentLabel = (UILabel *)[self.view viewWithTag:53];
    videosLabel = (UILabel *)[self.view viewWithTag:54];
    moreLabel = (UILabel *)[self.view viewWithTag:55];

    NSLog(@"resizeForPortrait called");

    webView.frame = CGRectMake(0, 44, self.view.frame.size.width, self.view.frame.size.height -88);
    button.frame = CGRectMake(0, 0, image.size.width, image.size.height);
    myLabel.frame = CGRectMake(145, 152, 80, 21);
    label.frame = CGRectMake(0, 0, 315, 40);

    NSLog(@"My view frame: %@", NSStringFromCGRect(self.view.bounds));

    NSLog(@"my status bar height is %f", [UIApplication sharedApplication].statusBarFrame.size.height);
    NSLog(@"my status bar width is %f", [UIApplication sharedApplication].statusBarFrame.size.width);


    float myWidth = MIN(self.view.bounds.size.height, self.view.bounds.size.width);
    float myHeight = MAX(self.view.bounds.size.height, self.view.bounds.size.width);

    newsLabel.frame = CGRectMake(newsLabel.frame.origin.x, myHeight - 18, 43, 21);
    weatherLabel.frame = CGRectMake(newsLabel.frame.origin.x + 43, myHeight - 18, 42, 21);
    sportsLabel.frame = CGRectMake(newsLabel.frame.origin.x + 97, myHeight - 18, 42, 21);
    entertainmentLabel.frame = CGRectMake(newsLabel.frame.origin.x + 138, myHeight - 18, 66, 21);
    videosLabel.frame = CGRectMake(newsLabel.frame.origin.x + 213, myHeight - 18, 42, 21);
    moreLabel.frame = CGRectMake(newsLabel.frame.origin.x + 258, myHeight - 18, 42, 21);


    spacer1.width = (myWidth - 346) / 2;
    spacer2.width = 20;
    spacer3.width = 18;
    spacer4.width = 25;
    spacer5.width = 28;
    spacer6.width = 14;
}

Called by:

-(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {

    if (toInterfaceOrientation == UIInterfaceOrientationLandscapeLeft || toInterfaceOrientation == UIInterfaceOrientationLandscapeRight) {
        [self resizeForLandscape];
    } else {
        [self resizeForPortrait];
    }
}

- (void) getOrientationandResize
{    
    if (UIDeviceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation)) {
        [self resizeForLandscape];
    } else {
        [self resizeForPortrait];
    }
}

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    [self getOrientationandResize];
}

My head is about to explode. Not only are the views reversed, but 20 pixels have been robbed from the width and given to the height. Can anyone explain this to me and how I could/should code the elements in my view to account for it?

My storyboard is as follows:

enter image description here

My initial view: enter image description here

After first rotation (still good) enter image description here

Rotating back to portrait (all screwed up because of the bounds values) enter image description here

Sonny Parlin
  • 931
  • 2
  • 13
  • 28
  • I would guess that your bounds are being set before the windows bounds are. As far as the 20px that's going to be the Status Bar at the top. – mkral Sep 13 '12 at 18:16
  • What methods are being called when you rotate from the delegate? Are you using IB? – mkral Sep 13 '12 at 18:19
  • I figured the 20px was the status bar, but why does it only show up after the two rotations? Shouldn't it be there from the beginning? Also, as an extra bit of weirdness, when I print out the status bar height after I rotate back to portait ([UIApplication sharedApplication].statusBarFrame.size.height), it says this: my status bar height is 568.000000. – Sonny Parlin Sep 13 '12 at 18:21
  • Out of curiosity, is the width 20px? – mkral Sep 13 '12 at 18:26
  • I just tested it, when you rotate it doesn't change the bounds of the statusbar, just rotates them – mkral Sep 13 '12 at 18:28
  • Is this an autorotating view? – Shizam Sep 13 '12 at 18:31
  • WHen my app starts, the statusbar width is 320, height is 20. WHen I rotate to landscape, it stays the same, when I rotate back to portait, it then becomes height: 568.000000 width: 20.000000. – Sonny Parlin Sep 13 '12 at 18:31
  • Shizam, yes, I believe so. i.e. I just call willRotateToInterfaceOrientation, check the orientation then adjust some elements. – Sonny Parlin Sep 13 '12 at 18:33
  • Are you using InterfaceBuilder to handle struts/springs? – mkral Sep 13 '12 at 18:34
  • Okay I added more code and screenshots to hopefully paint a more complete picture. – Sonny Parlin Sep 13 '12 at 18:46
  • How does `resizeForLandscape` get called? Show us the method that calls it. – rob mayoff Sep 13 '12 at 18:55
  • It's called by willRotateToInterfaceOrientation and viewDidAppear, I added the code to the question. – Sonny Parlin Sep 13 '12 at 19:00

1 Answers1

48

You should pretty much never do layout in willRotateToInterfaceOrientation:duration:. When the system sends you willRotateToInterfaceOrientation:duration:, it has not updated your view's frame to its new size. So by consulting self.view.frame, you're using the frame for the current (old) orientation, not the frame for the new (not-yet-rotated-to) orientation.

You should also not do layout in viewDidAppear:, because by the time you receive viewDidAppear:, your view is already on the screen. You need to do layout before your view is on the screen.

You need to do your layout in an appropriate method. The best place to do layout is in the layoutSubviews method of a custom UIView subclass. Putting it there keeps your view logic separate from your controller logic.

The next-best place to do your layout is in the viewWillLayoutSubviews or viewDidLayoutSubviews method of your view controller.

If you put your layout code in layoutSubviews, viewWillLayoutSubviews, or viewDidLayoutSubviews, the system will automatically call that method before your view appears on-screen, and inside the animation block of an autorotation. In both cases, it will update your view's frame for the correct orientation before sending you the message.

If you need to do something like hide a view before the autorotation animation starts, and show it again after, you can do that in willRotateToInterfaceOrientation:duration: and didRotateFromInterfaceOrientation:.

If you want to perform special animations only during an autorotation, and not during initial view layout, you can do that in willAnimateRotationToInterfaceOrientation:duration:, which is called inside the animation block of an autorotation.

You may find this answer helpful: UIViewController returns invalid frame?

Community
  • 1
  • 1
rob mayoff
  • 375,296
  • 67
  • 796
  • 848