Popping in a UINavigationController inside a UISplitViewController causes strange transition - ios

I am using a UINavigationController inside the Master View of a UISplitViewController. Inside of my UINavigationController I have, as usual, a UITableViewController. Selecting a cell in this table view pushes a new UINavigationItem onto the stack. This transition occurs as I expect. However, once I've pushed, when I push the Back button, the transition back to the top UINavigationItem doesn't slide from left to right as usual. Instead, the screen goes black, the Master View holding the UINavigationController/UITableViewController slides down in the center of the screen from the top, and then the UINavigationController appears back on the left side where I expect it. I've found similar questions, but all of the accepted answers revolve around not handling rotation correctly. I've double-checked that all of my view controllers return YES for all orientations.

implementing the following in my view controllers solved it for me
-(NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskAll;
}
Hope it works for you as well

You should check your implementation of - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation method in view controller that you have pushed into UINavigationController.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
{
// should return YES for all orientations
// or at least for orientation that your UINavigationController supports.
}
For me this issue appears only on iOS 5, but if you experience it on iOS 6 also, you could implement -(NSUInteger)supportedInterfaceOrientations in similar way.
I hope it will help you.

Related

popViewControllerAnimated: From Landscape To Portrait

I'm having trouble popping a UINavigationController (subclass) from a landscape view to a portrait view.
I have a chain set up from the window's rootViewController using shouldAutorotate and supportedInterfaceOrientations down to the individual View Controllers that I present.
So, I have a View Controller that ONLY supports portrait. The next one that is pushed supports landscape as well.
When I push the one that supports landscape and then rotate the device, everything rotates. So far, so good.
Now, when I pop back to the first one, both views rotate BEFORE the animation. So, the second viewController's landscape view (which is really small because of the rotation transform) is pushed away to the right side of the portrait screen.
I want the landscape view to be pushed away to its right (the top or bottom of the portrait view controller), while the portrait view controller is shown in the background.
How can I accomplish this?
I thought I might try to use an animation controller, but the UINavigationController's delegate method, navigationController:animationControllerForOperation: isn't called when popping to a View Controller in a different orientation.
After days of experimentation I figured out what my problem was. Hopefully this will help someone in the future.
I was trying to over-engineer the chain.
I had a custom container with a child tab bar controller which contained multiple navigation controllers that contained view controllers with different orientation requirements.
My main downfall was assuming that even if a modal window was presented, the system would still ask the window's root view controller first. To account for this situation I added a check in the container that looked like this:
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
if (self.presentedViewController) { // This check is wrong! Don't do this!
return [self.presentedViewController supportedInterfaceOrientations];
}
// Pass request to child view controller
}
However, this was fundamentally causing my problem in two ways.
First, without this check, UIKit will automatically detect the modal view controller and directly ask for its supportedInterfaceOrientations.
Second, the way UIKit handles the animations involves presenting several internal view controllers that we normally would have no idea are there. By checking for a modal view controller in presentingViewController, the check was catching them and asking them all for their supportedInterfaceOrientations. UIKit isn't meant to behave this way, so I was the one interfering with its operations.
The fix was to implement supportedInterfaceOrientations so that it ONLY concerns the view controller's direct descendants (i.e. the view controllers whose views are descendants in the current view controller's view hierarchy). The direct descendants can further pass the request down from there.
Treat modal windows as a completely separate chain, regardless of the presenter.
In other words, trust UIKit.

UISplitViewController's detail view push error

I'm implementing a universal app using UISplitViewController for iOS8 and facing strange problem with the UINavigation and would really appreciate your expertise.
My project has the following StoryBoard layout:
On iPad, everything is working as expected. However, running on the iPhone, the navigation doesn't work as expected. Please see this short video demonstrating the navigation problem as I navigate from "Detail Screen 2" back to "Detail Screen 1".
I tried implementing this same scenario on a brand new project but I did not see the problem. Only after porting into my existing project do I see this behavior.
UPDATE 1:
Here is my AppDelegate code:
#interface AppDelegate () <UISplitViewControllerDelegate>
#end
#implementation AppDelegate
-(BOOL) application: (UIApplication*) application didFinishLaunchingWithOptions: (NSDictionary*) launchOptions {
UISplitViewController *splitViewController = (UISplitViewController *)self.window.rootViewController;
UINavigationController *navigationController = [splitViewController.viewControllers lastObject];
navigationController.topViewController.navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem;
splitViewController.delegate = self;
splitViewController.preferredDisplayMode = UISplitViewControllerDisplayModeAllVisible;
return YES;
}
#pragma mark - Split view
- (BOOL)splitViewController:(UISplitViewController *)splitViewController collapseSecondaryViewController:(UIViewController *)secondaryViewController ontoPrimaryViewController:(UIViewController *)primaryViewController {
return YES;
}
....
#end
UPDATE 2:
Thanks to PetahChristian, I think his observation below is correct:
To collapse a secondary view controller which has a navigation
controller, Apple inserts the secondary navigation controller onto the
primary navigation controller's stack. So, for the iPhone, where you
see the problems, it looks like there is only one navigation
controller, but there actually are two.
Assuming that observation is correct, how can the secondary navigation controller be prevented from pushing onto the primary navigation controller? The UISplitViewControllerDelegate methods only handle collapse logic for secondary view controller DIRECTLY linked to the UISplitViewController. In my case, the secondary view controller to be collapsed (namely Detail VC1) is routed via "Show Detail (e.g. Replace)" segue from the master view controller and the UISplitViewControllerDelegate methods doesn't execute during this transition.
With the exact same setup on a brand new project, Apple doesn't insert the secondary navigation controller onto the primary navigation controller and I don't experience this problem on a new project.
Many thanks.
The culprit causing this navigation peculiarity in my project was due to an extension I downloaded from SO user called UIViewController+BackButtonHandler. This handler intercept the navigation back button so I can get the chances to do extra work when user press back. This category extension code overrides navigationBar:shouldPopItem: causing the default navigation to break. I had no idea this code was executing because I wasn't utilizing it but rather, just incorporating in my project. Wow... 2 days of banging my head against the wall.
It behaves differently between projects because the UISplitViewControllerDelegate code is different.
The new project has the necessary code, but the existing project may be missing it.
Check your AppDelegate and compare the code that handles collapsing and separating the secondary view controller.
-splitViewController:collapseSecondaryViewController:ontoPrimaryViewController:
-splitViewController:separateSecondaryViewControllerFromPrimaryViewController:
Update:
You shouldn't unconditionally return YES in splitViewController:collapseSecondaryViewController:ontoPrimaryViewController:. You need to first determine which view controller is on the top: See how they check to see if it is the (secondary navigation controller, and its child) detail view controller, and it has details to show?
- (BOOL)splitViewController:(UISplitViewController *)splitViewController collapseSecondaryViewController:(UIViewController *)secondaryViewController ontoPrimaryViewController:(UIViewController *)primaryViewController {
if ([secondaryViewController isKindOfClass:[UINavigationController class]] && [[(UINavigationController *)secondaryViewController topViewController] isKindOfClass:[DetailViewController class]] && ([(DetailViewController *)[(UINavigationController *)secondaryViewController topViewController] detailItem] == nil)) {
// Return YES to indicate that we have handled the collapse by doing nothing; the secondary controller will be discarded.
return YES;
} else {
return NO;
}
}
Update 2:
To collapse a secondary view controller which has a navigation controller, Apple inserts the secondary navigation controller onto the primary navigation controller's stack. So, for the iPhone, where you see the problems, it looks like there is only one navigation controller, but there actually are two.
In your video, when it looks like you are at the master, but it has a back button, and you tap it, and the back button slides off the screen, that's the secondary navigation controller disappearing from the primary navigation controller.
For your project to properly work, you'll have to conditionally test for the navigation controller, like apple does, to determine whether the (navigation controller and its child) detail view controller get discarded or not.
Update 3:
A navigation controller, in itself, is a view controller. It controls its children view controllers.
The secondary navigation controller is the secondary view controller. The detail view controller is its child view controller.
What the splitView delegate methods are handling are the collapse or separation of the secondary navigation controller, which happens to have one or more child view controllers.
When the splitView controller is collapsed, the primary navigation controller's stack looks like [masterViewController, secondaryNavigationController].
As for the replace segue, what you are replacing is the Empty detail view controller in the bottom of the Storyboard, with the orange Detail Screen 1. But the replaced detail view controller still has a parent, the secondary navigation controller. And when the splitViewController collapses, the secondary navigation controller ends up on the primary navigation controller stack. You don't see this, because it's transparent. All you see is the Detail screen 1 because it's the top view controller of the secondary navigation controller.
Your splitViewDelegate code is broken. This is why the view controllers don't animate properly in the video when you tap the Back button. Fix the delegate code, and all will look and work correctly.
You definitely need both the collapse and the separate methods, and they need to do the right thing. That's why I'm recommending you use Apple's code, instead of trying to write your own, because Apple's code correctly collapses and separates the navigation controller.

NavigationBar contents disappear on pop from view with prefersStatusBarHidden = YES

I have a fairly straightforward setup in my iPhone app, with a navigation controller and a view controller. The view controller has a title, and for most of my views, pushing other view controllers works as expected: the title is used as the label for the "back" button on the navigation bar, and the new view is shown. After the new view has been popped from the stack, the old view is shown with its title.
However, as soon as the pushed view controller implements prefersStatusBarHidden with the return value YES, the title in the navigation bar is gone after this view is popped from the stack - it remains empty and doesn't even display my custom rightbarbuttonitem.
Additionally, doing this in landscape instead of portrait does not show this behaviour - the title is displayed correctly. If you encounter this issue in portrait, you could turn the phone to landscape and back to portrait again, and the title and everything else will reappear in place.
I am unsure if this was already there in previous versions of iOS, but I am currently seeing it with iOS 8.
I had the same issue and the workaround for me was this:
In the view controller that is having prefersStatusBarHidden set to YES add:
- (void)viewWillDisappear:(BOOL)animated
{
[self.navigationController setNavigationBarHidden:YES];
[self.navigationController setNavigationBarHidden:NO];
}
I believe that is because prefersStatusBarHidden is a app wide setting, not per view controller.
You may be able to get around this by adding to the pushed view controllers ViewWillDisappear method
- (void)viewWillDisappear:(BOOL)animated {
[UIApplication sharedApplication].statusBarHidden = NO;
}

Navigation controller stack in landscape mode, but modally presented view controller view always in portrait frame size

My app mainly uses a navigation controller stack, and sometimes displays some controllers modally. In landscape mode the controllers within the nav controller stack work fine, but other view controller shown by presentViewController or previous presentModalViewController always give portrait size view frame (always 768x1024 on iPad iOS 6.0) - even when rotating back and forth between portrait and landscape.
Kind of related to A view controller is in landscape mode, but I'm getting the frame from portrait mode? however the checked answer is not helping. If I add the later view controller as part of the navigation stack the resize happening on first load and subsequent rotations work. The problem appears only, as stated above, when adding the controller by presentViewController.
wrap the modal branches to another nav controller and define the rotation mask there.
in iOS6 the - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation) works only on nav stack branches and entire branch should work the same way.
So, subclass the nav:
#interface CLNotRotatingNavController : UINavigationController
and in its .m add this
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return NO;
}
- (NSUInteger)supportedInterfaceOrientations {
return UIInterfaceOrientationMaskLandscape;
}
- (BOOL) automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers {
return YES;
}
and wrap all the modal branches to this nav. This will lock everything to lanscape where necessary.

iOS Push Navigation Controller, without a bar on the second view

I have an iOS App, designed within a UINavigationController. One of the pushed view controllers, however, needs a full screen view, without the navigation bar on the top. (to get back, there is just a small, circular button). However, any method I've tried of 'hiding' the navigation bar (navigationCtl.navigationBar.hidden=TRUE) leaves me with ugly artifacts - calling that before the view is pushed (in viewDidLoad or viewWillAppear) causes the previous view controllers bar to flash white just as the slide left animation starts. Similarly, calling it in viewDidAppear leaves a white bar at the top of the second view, along with several subviews pushed down, out of the way. Is there any way I can just have the new view slide over as it usually does, but when it comes over, there's simply no navigation bar up top?
Please note, to help Google, essentially the question here is:
How to animate between two UIViewControllers, when one has a navigation bar at the top, and the other one does not have a navigation bar at the top. So, how to navigate from a UIViewController with a navbar to one without a navbar - avoiding the horrible flickering.
The amazing answer is given below by Ev ... awesome.
give this a spin and see how it works for you.
in the destination view controller in viewWillAppear
- (void)viewWillAppear:(BOOL)animated {
[self.navigationController setNavigationBarHidden:YES animated:YES];
}
It actually has a cool effect and can be useful. in the viewWillAppear everything happens before the view is displayed so it takes away the strange artifacts.
be well

Resources