4
votes

I have a custom view controller that can contain two child view controllers. The view of one of the controllers is made visible when the device is in portrait orientation. The view of the other controller is made visible when the device is in landscape orientation. When the landscape orientation view is made visible, however, the status bar is retracted to make more room for the particular view. The status bar is unhid after the device is turned back into portrait mode. This is custom view controller is displayed within a UINavigationController.

My problem is that my subviews are not adjusting properly when the status bar's visibility changes. There ends up being a big gap and/or overlap when you turn the device in the different orientations as pictured below: enter image description here

As you can see, it is initially fine (in portrait), but when the device is turned, there is a white gap where the status bar was. When the device is turned back to portrait, the UINavigationController's navigation bar is brought up and overlapped by the status bar and a gap between navigation bar and the view below it appears. If you are very quick to rotate 180 degrees from one landscape orientation to the opposite landscape orientation, the gap disappears and looks good.

The method below belongs to the custom view controller and is called in willAnimateRotationToInterfaceOrientation:duration: (obviously to handle rotation events) and viewDidAppear: (to handle when the view pushed in from the previous view controller in the navigation stack).

- (void)cueAnimationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation fromViewDidAppear:(BOOL) fromViewDidAppear
{
    // Fading animation during orientation flip.
    // Make sure its not already animating before trying.
    BOOL barHidden =  [UIApplication sharedApplication].statusBarHidden;
    if (!isAnimating) {
        BOOL alreadyGoodGrid = (UIInterfaceOrientationIsLandscape(interfaceOrientation) && curView == self.gridViewController.view);
        BOOL alreadyGoodTable = (UIInterfaceOrientationIsPortrait(interfaceOrientation) && curView == self.tableViewController.view);
        if ((alreadyGoodGrid && barHidden) ||
            (alreadyGoodTable && !barHidden)) {
            // If views are the way they should be for this orientation. Don't do
            // anything.
            return;
        }
        isAnimating = YES;
        UIView *nextView;
        // Get starting orientation. This will determine what view goes on top
        if (UIInterfaceOrientationIsLandscape(interfaceOrientation))
            nextView = self.gridViewController.view;
        else
            nextView = self.tableViewController.view;

        if (nextView == self.tableViewController.view)
        {
            if (!alreadyGoodTable)
            {
                self.tableViewController.view.alpha = 0.0;
                [self.view bringSubviewToFront:self.tableViewController.view];
            }
            // Unhide the bar for the table view
            [[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationSlide];
        }
        else // gridViewController
        {
            if (!alreadyGoodGrid)
            {
                self.gridViewController.view.alpha = 0.0;
                [self.view bringSubviewToFront:self.gridViewController.view];
            }
            // Hide the bar for the grid view
            [[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:UIStatusBarAnimationSlide];
        }
        [UIView animateWithDuration:0.4
                              delay: 0.0
                            options: UIViewAnimationOptionAllowUserInteraction
                         animations:^{
                             if (nextView == self.tableViewController.view) {
                                 self.tableViewController.view.alpha = 1.0;
                             }
                             else {
                                 self.gridViewController.view.alpha = 1.0;
                             }                             
                         }
                         completion:^(BOOL finished) {
                             if (nextView == self.tableViewController.view) {
                                 curView = self.tableViewController.view;
                             }
                             else {
                                 curView = self.gridViewController.view;
                             }
                             isAnimating = NO;
                         }];
    }
}

Much thanks to anyone that can take the time to look at this.

5

5 Answers

2
votes

A lot of people seem to have this problem, as I have, and there are other Q/A threads about it which you ought to search for -- I may never have found the magic answer. One kludge you might try that seems to "fix" the problem in some circumstances is when you change the bar visibility to (re)show the status bar:

  • Hide the nav bar.
  • Un-Hide the status bar.
  • Un-Hide the nav bar.

Or Hide, Hide, Un-Hide if you're hiding the status bar.

Sometimes people have found that a slight delay is required before Un-Hiding the nav bar -- so do that in an async dispatch block to run on the next runloop. Something like:

dispatch_async(dispatch_get_main_queue(), ^{
        [self.navigationController setNavigationBarHidden:NO animated:NO];
});
0
votes

After fiddling around a lot I seemed to have come up with some kind of solution. It seems to work without fail.

Here is my code:

- (void)cueAnimationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation fromViewDidAppear:(BOOL) fromViewDidAppear
{
    // Fading animation during orientation flip.
    // Make sure its not already animating before trying.
    BOOL barHidden =  [UIApplication sharedApplication].statusBarHidden;
    if (!isAnimating) {
        BOOL alreadyGoodGrid = (UIInterfaceOrientationIsLandscape(interfaceOrientation) && curView == self.gridViewController.view);
        BOOL alreadyGoodTable = (UIInterfaceOrientationIsPortrait(interfaceOrientation) && curView == self.tableViewController.view);
        if ((alreadyGoodGrid && barHidden) ||
            (alreadyGoodTable && !barHidden)) {
            // If views are the way they should be for this orientation. Don't do
            // anything.
            return;
        }
        isAnimating = YES;
        UIView *nextView;
        // Get starting orientation. This will determine what view goes on top
        if (UIInterfaceOrientationIsLandscape(interfaceOrientation))
            nextView = self.gridViewController.view;
        else
            nextView = self.tableViewController.view;

        if (nextView == self.tableViewController.view)
        {
            if (!alreadyGoodTable)
            {
                self.tableViewController.view.alpha = 0.0;
                [self.view bringSubviewToFront:self.tableViewController.view];
            }
            [[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationSlide];
        }
        else
        {
            if (!alreadyGoodGrid)
            {
                self.gridViewController.view.alpha = 0.0;
                [self.view bringSubviewToFront:self.gridViewController.view];
            }
            [[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:UIStatusBarAnimationSlide];
        }
        [UIView animateWithDuration:0.4
                              delay: 0.0
                            options: UIViewAnimationOptionAllowUserInteraction
                         animations:^{
                             CGRect r = self.navigationController.navigationBar.frame;
                             if (nextView == self.tableViewController.view) {
                                 self.tableViewController.view.alpha = 1.0;
                                 self.navigationController.navigationBar.frame = CGRectMake(0.0, 20.0, r.size.width, r.size.height);
                                 self.view.frame = CGRectMake(0.0, 20.0, self.view.frame.size.width, self.view.frame.size.height - 20.0);

                             }
                             else {
                                 self.gridViewController.view.alpha = 1.0;
                                 self.navigationController.navigationBar.frame = CGRectMake(0.0, 0.0, r.size.width, r.size.height);
                                 double y = 0.0, h = 0.0;
                                 if (fromViewDidAppear)
                                 {
                                     y = -20.0;
                                     h = 20.0;
                                 }
                                 self.view.frame = CGRectMake(0.0, y, self.view.frame.size.width, self.view.frame.size.height + h);
                             }                             
                         }
                         completion:^(BOOL finished) {
                             if (nextView == self.tableViewController.view) {
                                 curView = self.tableViewController.view;
                             }
                             else {
                                 curView = self.gridViewController.view;
                             }
                             isAnimating = NO;
                         }];
    }
}

What I do is to check whether the method is being called from viewDidAppear: or somewhere else (in this case only willAnimateRotationToInterfaceOrientation:duration:). Things behave differently depending on who calls the method. In either case I adjust self.view.frame and self.navigationController.navigationBar.frame to account for differences in the status bar location. When we're coming from viewDidAppear: I need to throw in a negative y value of the height of the status bar to get the subview to move up properly. This seems all seems to work rather well.

0
votes

I had the same problem with the status bar and navigation bar overlapping after they were hidden then shown again. I found a solution on github here (see showUI method) that I modified to suit my needs.

- (void)toggleNavBar:(BOOL)hide
{    
    navBarHidden = hide;

    //Fade status bar
    [[UIApplication sharedApplication] setStatusBarHidden:hide 
                                            withAnimation:UIStatusBarAnimationFade];

    //Fade navigation bar
    [UINavigationBar beginAnimations:nil context:nil];
    [UIView setAnimationDuration:0.3]; // Approximately the same time the status bar takes to fade.

    self.navigationController.navigationBar.alpha = hide ? 0 : 1;

    [UINavigationBar commitAnimations];

    // Being invisible messes with the position of the navigation bar, every time it gets 
    // visible we need to set it to the correct y-offset (just below the status bar)
    [self adjustNavBarOrigin];
}

-(void)adjustNavBarOrigin
{
    CGRect r = self.navigationController.navigationBar.frame;
    r.origin.y = 20; //Height of the statusbar, can't find a reliable method in the SDK to get this value
    self.navigationController.navigationBar.frame = r;   
}
0
votes

for ios7, override prefersStatusBarHidden

- (BOOL)prefersStatusBarHidden {
    return NO if landscape and NO if landscape;
}

on orientation change call the function below to trigger the prefersStatusBarHidden function

[self setNeedsStatusBarAppearanceUpdate];

see the detailed answer in https://stackoverflow.com/a/19194901/1939554

0
votes

This solved my problem:

self.automaticallyAdjustsScrollViewInsets = false