3
votes

I have a UINavigationController with a UIViewController set as it's rootController, it contains a background on its UIView using an image set just under the navBar. I then push onto the navigation controller a new UIViewController and when the back button is pushed, the previous controller looks different. Using the visual debugger I can see that the self.view has moved entirely down below the navBar where previously it was at the top. I have no idea and been racking my brains as to why this might be happening Before Navigation stack Push

enter image description here

-(void)pushIPhoneMessagingContactsController:(MessageContactsViewController *)contactsController{
    self.selectorView.hidden = YES;
    [self.navigationController pushViewController:contactsController animated:YES];
}

On the RootViewController (iPhoneMessagingNotificationsController)

-(void)viewWillAppear:(BOOL)animated{
    self.selectorView.hidden = NO;
    [[[self navigationItem] leftBarButtonItem] setTintColor:[UIColor blackColor]];
    [[UIApplication sharedApplication]      setStatusBarStyle:UIStatusBarStyleDefault];

    if ([_displayType intValue] == MESSAGES_SHOWING) {
        [self.notificationsViewController.view removeFromSuperview];
        [self.contentView addSubview:_messagesViewController.view];
    } else {
        [self.messagesViewController.view removeFromSuperview];
        [self.contentView addSubview:_notificationsViewController.view];
    }

}
2
Some other code is affecting the view. Are you hiding or changing anything during the transition, which would affect the top layout, navigation bar, inset, or constraints? Please show any related code, such as viewWillAppear. To answer your question, we'd need more details to understand what the selectorView is, and how/where it appears in the view hierarchy.user4151918
The selectorView is a view that sits on top of the navbar and allows the user to switch between two subviews. it is added just after the rootViewController is set, it has to be hidden when other Controllers are pushed. There is some code in viewWillAppear I will add to it to the question.Md1079
I have tested the views Frame in viewWillAppear and it shows the view becomes (0,64,320,504) whereas initially it is (0,0,320,504) So something is modifying it somehwereMd1079
And Just to further confuse the issue, If I create a simple UIViewController with a redBackground as a test and push it instead, this issue does not happen, It's almost as if the pushed Controller is the thing causing the issue, although this should never effect the layout of other controllers on the stack I would have thought?Md1079

2 Answers

2
votes

It seems the offending line was in the viewWillAppear method of the pushed UIViewController

self.navigationController.navigationBar.translucent = YES;

Somewhere else this navigationBar gets set as translucent:

 [self.navigationController.navigationBar setBackgroundImage:[UIImage new]
                                              forBarMetrics:UIBarMetricsDefault];
 self.navigationController.navigationBar.shadowImage = [UIImage new];
 self.navigationController.navigationBar.translucent = YES;

and to make it solid colour again:

 self.navigationController.navigationBar.shadowImage = nil;
 self.navigationController.navigationBar.translucent = NO;

but this code seems to mess with the layout so perhaps there is another way to change the opacity of the navBar and statusBar without affecting the layout?

1
votes

What you're currently trying to do is hide or show a selectorView which really only should appear for one specific view controller.

Here's an encapsulated way to solve this that makes your selectorView a part of the root view controller, removing the connection from other view controllers. They no longer have to know about it or hide it.

Add your selectorView to your rootViewController's navigation bar titleView. (You can do this in code, or drop it in Storyboard and add an IBOutlet for it.)

self.navigationItem.titleView = selectorView;

Now when you push another view controller, its title will replace your rootViewController's selectorView title (view). Your other view controllers don't need to know anything about that view.

This is a good design approach in general. Anytime you have a control that should only appear on one view controller's navigation bar, you want to make it a part of that view controller's navigationItem (titleView, or left/right bar button items.) iOS will display the control when it presents that view controller, and hide the control when that view controller is no longer the top view controller in the navigation controller stack.

As for the 64-pixel height issue, it's likely related to some complexity in the rootViewController hierarchy that shouldn't be there.

In iOS 7/8, a view's content, by default, appears under a translucent navigation bar. Apple freely managed this for you, by insetting the first view of the view hierarchy.

From your code, it appears that you're trying to "hide" or "show" the (un)selected viewController's view.

Each view controller should have a view it controls. A view controller shouldn't be trying to control other view controller's views, or adding other view controller's views to its own view hierarchy.

Here's Apple's recommended way to approach this. Use a containerView in your rootViewController. The whole purpose of a container view is to encapsulate a view controller within a view. As your selectorView changes which view to show, you have your container view transition from one view controller to the other. (If you're not familiar with how to do that, check out this answer.)

Pin the containerView to the rootViewController's view, so Auto Layout can size it for you.

Your view hierarchy now looks like view -> containerView, instead of view -> hidden view of unselected view controller, shown view of selected view controller. Apple can adjust the first view's inset, and nothing gets incorrectly offset (by the height of the navigation control).

Update:

This question talks about scrollViewInsets and how they can be set on a view-controller-by-view-controller basis. If you do have a view controller, and you don't want its content to appear under a bar, uncheck that box.

But the best way to handle this is to "standardize" your UI, so it isn't varying from view to view. Either make the bar always be translucent, or not always be translucent. This makes transitions less "jarring" for the users.