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:
Related
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.
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
}
I am using Storyboards for an iOS 7 App. The root controller is a menu. For almost every view, I have a Menu button which brings the App back to that menu using [self.navigationController popToRootController:TRUE]. However, there are two UIView elements where I would like to clear the Navigation Controller view list (as happens when you pop back to the root controller), but then immediately go to another UIView without having the user see the root controller's view. Once at this new view, if the user presses the back button, I want them to go to the menu view.
I've tried to put a performSegue in the viewWillAppear, but it really messes up the Navigation Controller and views. I've tried putting a performSegue in the viewDidAppear, but the user sees first the Menu view flash in, then out on it's way to the correct view.
I hope I've explained this well enough. I hope this can be done.
Thanks.
Your best bet is to build the navigation controller stack yourself, and then use - (void)setViewControllers:(NSArray *)viewControllers animated:(BOOL)animated: to replace the current stack.
So you could do something like
...
UIViewController *vcToPush = ...;
NSArray *newVCStack = #[[self.navigationController.viewControllers firstObject], vcToPush];
[self.navigationController setViewControllers:newVCStack animated:YES];
This will add your new controller to the stack using the standard push animation (or not if you so choose), and after the animation is complete, set the view stack to that of the array.
I've looked everywhere for this, so as a last resort I figured I should ask the question.
I'm using Storyboard in XCode and have a navigation controller as my initial view. The root view of this navigation controller is a table view. In this table view, I have a button in the navigation bar that flips the view horizontally via a modal segue to a navigation controller. The root view of this controller is my map view. Now, when I want to dismiss this map view using [self dismissViewControllerAnimated:YES completion:nil] it doesn't work. It does if I take out the navigation controller. So, I'm guessing the dismissViewController is getting passed to the navigation controller, and not doing anything. I know I could implement a class for the navigation controller to handle the call and pass it on, but I don't want to do that unless absolutely necessary.
So to solve this question:
I need a way to switch between table view to map view using the flip horizontally animation and vice versa.
I need to have a navigation controller in both views, I could either be the same one, or a different one. I can't seem to find a way to use the same one, since any transitions would be pushes which don't do the flip horizontally transition.
Thanks in advance for the help!
Who is self when you call [self dismissViewControllerAnimated:YES completion:nil]?
The receiver of this message needs to be the view controller which presented the modal view controller you want to dismiss. If you call it on the presented view controller, or (in the case of container view controllers like UINavigationController, one of the view controllers it manages), UIKit will try to do the right thing but won't always succeed.
Presuming the self you refer to is the view controller containing the map view, you can get the presentingViewController property to get the object you should call dismissViewControllerAnimated:completion: on. (And if you need to get the navigation controller that's in between, use the navigationController property.)
In my app delegate I create a UISplitViewController. I set the delegate to be the detailViewController.
When I run my app in portrait, I have the left top popover button showing that will slide out the split view master.
Then I have a button in my detail view that resets the splitviewcontroller array with a new detail view controller and sets the split view delegate to that controller.
The second detail view displays properly... but I lose my popover button on the second view controller.
Does anyone know how I can get that button to remain on all of my detail view controllers I may add?
Thanks!
See http://www.raywenderlich.com/forums/viewtopic.php?f=2&t=1546 for what I find to be a good approach.
It involves setting the SplitViewController delegate to be the master instead of the detail. The master keeps references to the popoverController and the button, and each time the delegate methods are called (hide and show master) it gets the current detail view and performs the necessary action (add in the button/remove button and popovercontroller).
The master defines a protocol for "SubstituableDetailView" which contains the two methods for showing/hiding the button.