UISplitViewController's detail view push error - ios

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.

Related

Hide Master View Controller in UISplitView

I've got a bar button item on the navigation bar in my master view controller that appears when using a device in portrait. I want to be able to tap that item and hide the master controller even if a selection isn't made from the master controller table view. I can't seem to find any way of doing this. How can it be accomplished?
Call UISplitViewController showDetailViewController:sender:
If you cannot, I think you need to implement:
- (BOOL)splitViewController:(UISplitViewController *)splitViewController
collapseSecondaryViewController:(UIViewController *)secondaryViewController
ontoPrimaryViewController:(UIViewController *)primaryViewController
and return NO when you want to show the detail view.
EDIT: Actually -- I just looked at one of my projects. Implementing that delegate is for when you are doing things like rotating an iPad in split screen (and it's separating/collapsing automatically)
For my project, I keep track of the last detailVC and use that to call showDetailViewController:sender:

Dismiss Modally presented view makes tab bar controller (kind of) reset

I have an app which has tab bar controller as main controller. Each tab has a series of views with navigation controller and I normal push and pop those view in stack.
Weird problem is
Case 1 : If I create a UINavigationController and make a new viewController as its root, and present this NavigationController. Within this new navigation stack, I can easily present a view modally and dismiss it without a problem.
Case 2: Now without make a new UINavigationController, I present a view, and when I dismiss a view, the view beneath is behave weirdly. For example, it's the presenting view was UICollectionView, it just scroll back to 1st cell, like it's doing "reload" action and "scrollTo" the first cell. If the presentingView is a pushed view from rootView, it will just popToRoot view, which is definitely not intended.
I didn't have this problem until I implement UITabbarController, so I guess, I should know more that's going on under the hood when presenting a view and dismiss a view in UITabbarController.
I GUESS, when dismiss a view in UITabbarController view, it sort of "RESET" everything to the very first view of it's current tab. I really am not sure it's trure though.
I know it's kind of conceptual, but I can't help to think there must be something critical I am missing here.
I made silly mistake that I sublclass UITabbarController and define navigation controlllers in viewDidAppear instead viewdidLoad, so when I make the window's rootview to tabbar controller, the navigation controllers are not set properly. That's why all punky things happened. It would be nicer if just crash instead of this weird behaviors.
You can try this to go back to your first viewcontroller.
- (IBAction)buttonPressedFromVC2:(UIButton *)sender
{
[self dismissViewControllerAnimated:YES completion:nil];
} // This is going back to VC1.
This method is will be in second viewcontroller.m file. It is button click method.

going back inside navigation controller through embedded view controller

In my iOS project I have a main menu that is shown embedded in a container in my initial UIViewController.
After the user choses any row in that menu, the navigation controller pushes the submenu viewController that manages further actions, which uses the full window.
If the user wants to go back to main screen, he taps "back" button and my navigationController pops back. But when it should pop to the main viewController it fails to restore the view of my initial viewController.
Do you have any clue how to pop back to the first viewController in navigationViewController hierarchy if that view controller has containers with embedded view controllers in them?
Or should I consider changing the architecture of my storyboard?
The fact that the view controllers in a navigation controller have child view controllers is not important. Only worry about the top-level view controllers that are pushed onto the navigation controller's stack. And only push/pop top-level view controllers, not children.
If you are having problems, you are probably doing something wrong, and will need to post a screenshot of your storyboard, along with the code that shows how you manage your navigation controller stack.
If you want your initial view controller to contain the proper subviews, you either need to hide/show what you need to make it to look like you want in viewDidDisappear as the user moves on to a new view, or you need to set it when they come back in viewWillAppear.
However your view is set up when you leave is how it will show up when you come back unless you change it. For example, in your root view controller:
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
// hide your menu, clean up the view to prepare it for when user pops back
}
OR
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// if menu is showing, hide it. Clean up view before user sees it
}

Tabs do not show when using PushViewController

I'm trying to use the following code to display a tabbarcontroller
UITabBarController *tc = [[self storyboard] instantiateViewControllerWithIdentifier:#"tabbarcontroller"];
[self.navigationController pushViewController:tc animated:YES];
It does load the view, and I can tell it which of the tabs I want it to default to. The problem is the tabs don't show. From what I've read I gather it has something to do with putting the tab controller inside of the navigation controller, but I couldn't find any solutions.
If you use Storyboard, use pushViewController method is a bad choice (also if it work). You have to insert a "segue".
Go in the storyboard and while press ctrl button, click on the main controller (which must open the tabViewController) and then release the click on the tabBarController.
Now you have the segue. Click on the circle which appears and choose an identifier for this segue, for example: MainToTab .
Now in your method, you have just to call:
[self performSegueWithIdentifier:#"MainToTab" sender:self];
Moreover, if you want manage the property on the destination controller (by segue), you can implement this method:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"MainToTab"]) {
UITabViewController *tb = (UITabViewController *)segue.destinationViewController;
//set the properties...
}
}
This method is called automatically when you launch the previous method.
What you want inside your UITabBarController are UIViewControllers, possibly inside a UINavigationController.
You shouldn't as well push a UITabBarController. You can see Apple's explanation here:
An app that uses a tab bar controller can also use navigation
controllers in one or more tabs. When combining these two types of
view controller in the same user interface, the tab bar controller
always acts as the wrapper for the navigation controllers.
The most common way to use a tab bar controller is to embed its view
in your app’s main window. (...)
Still you can present it modally:
It is possible (although uncommon) to present a tab bar controller
modally in your app. Tab bar interfaces are normally installed in your
app’s main window and updated only as needed. However, you could
present a tab bar controller modally if the design of your interface
seems to warrant it. For example, to toggle from your app’s primary
operational mode to a completely different mode that uses a tab bar
interface, you could present the secondary tab bar controller modally
using a crossfade transition.

iOS 5 split view modal view controller popup: possible or no dice?

I basically have a splitview controller and immediately I would like to show a popup modal view controller.
I have wired up the UISplitView class with a modal segue to my other view controller (LoginView, just a straight UIViewController subclass) I basically just want to show that on load and I'm pretty sure I shouldn't do this in the app delegate (however I could be wrong)
I want to do it with a
[something performSegueWithIdentifier:#"login" sender:something];
Where should I put it and what should I connect the segue to (I swear I have tried every different combination haha!)
(I'm using the universal master-detail view starting project from Xcode 4.2)
I would display this from your initial detail view controller (the right pane of your split view) since it will always be sent a -viewDidAppear: message regardless of launch orientation.
In your -viewDidAppear: method, have the split view controller present the modal controller. Each view controller in a split view controller will already have its splitViewController property set. Ensure that your segue is connected from the split view controller (not one of its child view controllers) to the login view controller.
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self.splitViewController performSegueWithIdentifier:#"login" sender:self.splitViewController];
}

Resources