views:

176

answers:

1

So I have this iPhone app with a nested setup like this: [RootViewController]->[UITabBarController]->[UINavigationController]->[subViewControllers]. Everything is built programmatically, not in Interface Builder.

I'm simply trying to support all device orientations. All of my AutoResizing masks are set up and everything rotates and expands/contracts as necessary. Everything except for the NavigationController's navBar and Toolbar. (Normally, these views auto-resize automatically depending on the orientation. i.e. In landscape, the navbar and toolbar should automatically resize to about 32px, and 44px in Portrai mode.) What's peculiar is that the subView of the navigation controller is resizing, but the actual navBar and toolbar are not. In other words, the content stretches/shrinks about 12 px or so in whichever direction as though the navbar and toolbar resized. This appears to be the autoresizing masks doing their job.

So in order to attempt to solve this situation, I created a category for UINavigationController that responds to 'willRotateToInterfaceOrientation:duration:' in order to trigger the 'sizeToFit' selector. (This technique came from another post on Stack Overflow, with a similar issue.) I discovered, in my research, that only the outermost view controller receives this message, however, so I have my root view controller call this on tab controller, which in turn calls it on the nav controller, etc. This took care of that problem, and now my nested view controllers are being notified when the outer view is rotated. Using the sizeToFit technique, both the nav bar and toolbar are resizing on their own. However, that still leaves the issue of repositioning the toolbar, in order to make up for the offset from the size change. (Since our view coordinates in iPhone start at top left.)

What makes this tricky to do within the willRotate method, is that we have no way of knowing what the new bounding view dimensions will be, nor do we know what our current orientation is. (Unless you have deviceOrientationNotifications turned on, which I don't.) All we have to work with is what the new orientation will be, and what our toolbar's current frame values are. I could hard-code this and calculate frame adjustments the old-fashioned way using a piece of scratch paper, but I really want to retain the dynamic view sizing features without making any assumptions about the device's screen dimensions.

Therefore, using the following code, I managed to remedy this by simply 'bumping' the toolbar by 12px relative to the size it just became. In order to prevent it from over-bumping when the user flips the device, I use the current toolbar's frame height to derive the current orientation. (This means I still am making an assumption regarding the toolbar's before and after sizes, but I feel better about that since I can influence that to a degree as opposed to trying to programmatically resize the device's physical screen.)

@implementation UINavigationController (BLUINavigationControllerAutoRotate)

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
{
    return TRUE;
}

- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
    [super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];

    // Resize the navBar
    [[self navigationBar] performSelector:@selector(sizeToFit)
                               withObject:nil
                               afterDelay:(0.5f * duration)];

    // Read our current frame size
    CGRect toolbarFrame = self.toolbar.frame;

    // Prevent over adjusting when flipping the device
    if ((toolbarFrame.size.height < 38
         && (toInterfaceOrientation == UIInterfaceOrientationLandscapeLeft
             || toInterfaceOrientation == UIInterfaceOrientationLandscapeRight))
        || (toolbarFrame.size.height > 38
            && (toInterfaceOrientation == UIInterfaceOrientationPortrait
                || toInterfaceOrientation == UIInterfaceOrientationPortraitUpsideDown)))
    {
        // We are already resized and repositioned.
        return;
    }

    // Calculate and apply the toolbar offset
    const NSInteger delta = 12;     // the difference in size between 44 and 32 px.
    toolbarFrame.origin.y += ((toInterfaceOrientation == UIInterfaceOrientationLandscapeLeft)
                              || (toInterfaceOrientation == UIInterfaceOrientationLandscapeRight)) ? (delta) : (-delta);
    self.toolbar.frame = toolbarFrame;

    // Resize the toolbar
    [[self toolbar] performSelector:@selector(sizeToFit)
                         withObject:nil
                         afterDelay:(0.5f * duration)];

}

@end

So, now I have properly behaved NavigationBar and Toolbar. The end-user will never know that I had to implement all of this extra work-around in order to re-enable what seems like is supposed to be a standard behavior. So my question is, why do I have to do all of this? Is there a better approach I should know about, or something that I simply am not doing elsewhere? (i.e. [navigationController setAutomaticallyResizesToolbarOnRotate:TRUE]; or something like that?) Like I said, this seems like it's supposed to be pre-wired, so for me to have to finagle with forwarding messages and forced triggers feels like an over-engineered hack rather than the proper solution.

Thanks!

A: 

You're passing on the willRotateToInterfaceOrientation message to the tab view controller from your root view controller - why not pass on all the other rotation messages and see if that helps? i.e.

- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
    [super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
    [tabBarController willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
}

- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
    [super willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration:duration];
    [tabBarController willAnimateRotationToInterfaceOrientation:toInterfaceOrientation duration:duration];
}

- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation  {
    [super didRotateFromInterfaceOrientation:toInterfaceOrientation];
    [tabBarController didRotateFromInterfaceOrientation:toInterfaceOrientation];
}

- (void)willAnimateFirstHalfOfRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
    [super willAnimateFirstHalfOfRotationToInterfaceOrientation:toInterfaceOrientation duration:duration];
    [tabBarController willAnimateFirstHalfOfRotationToInterfaceOrientation:toInterfaceOrientation duration:duration];
}

- (void)didAnimateFirstHalfOfRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
    [super didAnimateFirstHalfOfRotationToInterfaceOrientation:toInterfaceOrientation];
    [tabBarController didAnimateFirstHalfOfRotationToInterfaceOrientation:toInterfaceOrientation];
}

- (void)willAnimateSecondHalfOfRotationFromInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
    [super willAnimateSecondHalfOfRotationFromInterfaceOrientation:toInterfaceOrientation duration:duration];
    [tabBarController willAnimateSecondHalfOfRotationFromInterfaceOrientation:toInterfaceOrientation duration:duration];
}

I'd be interested to see what happens if you pass all these to your tab bar controller - your problem seems like something that should happen automatically, instead of being all the extra work you've done!

deanWombourne
Yes! That works. Actually, it only took adding 'willAnimateRotationToInterfaceOrientation:' to make it work. I already had willRotate and didRotate. However, if you use 'willAnimateRotationToInterfaceOrientation:' you should not use either 'willAnimateFirstHalfOfRotationToInterfaceOrientation: or 'willAnimateSecondHalfOfRotationToInterfaceOrientation'. Same goes for the 'didAnimate...' methods. Anyway, long story short, (literally) that's all it takes. Thanks! :)
GmanYRU