I am developing an iOS application that uses a left side slide out drawer containing tabs, each representing one of the main views of the app. Currently, when the user selects a tab the application searches through the navigation stack for an instance of the relevant view controller and if it finds one pops back to that controller, otherwise it creates a new instance and pushes it onto the stack.
I would like to also add a back button allowing the user to go back to the previous view, however since many navigation options will pop the user to a previous view controller resulting in the controller they are leaving being dealloc'ed there is no obvious way to have a back button to get back to that controller again.
Is there any way to structure this application so that a back button can be added, while still allowing the user to use the tabs to navigate to any view at a given time?
An example of the navigation code follows (invoked when a user clicks one of the tabs):
if(![self.navigation.topViewController isKindOfClass:[GraphViewController class]]) { //Are we already in this view?
BOOL foundController = NO;
for(id controller in self.navigation.viewControllers) { //Is there a controller of this type already in the stack?
if([controller isKindOfClass:[GraphViewController class]]) {
[self.navigation popToViewController:controller animated:YES];
foundController = YES;
break;
}
}
if(!foundController) {
GraphViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:#"graphViewController"];
controller.connection = _connection;
controller.data = _dataCache;
[self.navigation pushViewController:controller animated:YES];
}
}
I believe what you want is navigation controllers for each item in the slide out menu. This way when the user selects a view from the side menu they can navigate the views associated with that section. This will allow the user to go back from a view once they have selected a item from the side menu.
Related
I have a login screen as my root view controller. If the user is already logged in, I want to skip over the login screen and show the main view controller.
The code below does that, but it shows a back button in the nav bar instead of the default nav bar.
Is there a way to remove the back button? There is a menu button that is supposed to be there, so simply hiding the back button will not suffice.
Thanks!
- (void)viewDidLoad {
if(GetUserName != nil){
[self pushingView:YES];
}
[super viewDidLoad];
}
-(void)pushingView:(BOOL)animation{
MainViewController *revealViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"RevealViewController"];
[self.navigationController pushViewController:revealViewController animated:animation];
}
Don't try to remove the back button, instead remove the need for the back button. Instead of using pushViewController:animated: use setViewControllers:animated:. In this way you only have one view controller on the stack (and you save the memory of keeping the view controller in existence).
Ideally you wouldn't even load the login view controller and then you'd save even more memory and time.
I have three View Controllers in a Navigation Controller. One is the root, and the others should appear if conditionA or conditionB is met, respectively.
In case both conditions are true, I want the conditionA View Controller to be shown. If things then change, and conditionA is no longer met, but conditionB still is, I want to show the conditionB View Controller. This means that I want to be able to go from the conditionA View Controller to the conditionB View Controller, and vice versa, until neither condition is met. However, I always want the back button to send users to the root.
To implement this while avoiding errors like ‘Finishing up a navigation transition in an unexpected state’, I’ve taken code from Dave DeLong’s answer https://stackoverflow.com/a/11821263/2524427.
(void)somethingChanged:(NSNotification*)notification
{
if(!conditionA && !conditionB){return;}
NSArray *viewControllers = self.navigationController.viewControllers;
NSMutableArray *newViewControllers = [NSMutableArray array];
// preserve the root view controller
[newViewControllers addObject:[viewControllers objectAtIndex:0]];
if(conditionA)
{
// add the new view controller
[newViewControllers addObject:[self.storyboard instantiateViewControllerWithIdentifier:#"conditionAViewController"]];
// animatedly change the navigation stack
[self.navigationController setViewControllers:newViewControllers animated:YES];
return;
}
if(conditionB)
{
// add the new view controller
[newViewControllers addObject:[self.storyboard instantiateViewControllerWithIdentifier:#"conditionBViewController"]];
// animatedly change the navigation stack
[self.navigationController setViewControllers:newViewControllers animated:YES];
}
}
This works almost perfectly. However, if I have the following sequence
somethingChanged triggered while only conditionB is met -> conditionB View Controller appears - as desired
somethingChanged triggered again, but now both conditionA and conditionB are met -> condition A View Controller appears - as desired
somethingChanged triggered for the third time, now back to initial situation where only conditionB is met -> I return to the root View Controller (with Back button) instead of conditionB View Controller…
How can I keep going back and forth between these View Controllers?
Instead of the solution in the question you linked. Try this
init your own back button UIBarButtonItem as navigationBar's leftBarButtonItem in both conditionA View Controller and conditionB view controller
wire up with a action
- (IBAction)dismiss:(id)sender {
[self.navigationController popToRootViewControllerAnimated:YES];
}
So I have a navigation controller leading to a table view, which leads to a detail view. There is an add button leading to another table view to choose from a contacts list. When I select DONE, I want to lead to a new detail view, but the navigation bar won't display. Below is an image of the IB:
So I want to move from the Contacts View Controller to the Convo View Controller but keep the navigation item working.
The code that is executed when the DONE button is pressed:
- (IBAction)doneButtonPressed:(UIBarButtonItem *)sender {
BOOL sharing = NO;
if ([self.referrer isEqualToString:#"mapView"]) sharing = YES;
Convo *newConvo = [[Convo alloc] initWithMembers:self.selectedContacts
sharing:sharing];
// add newConvo to convos list
ConvoViewController *convoVC = [self.storyboard
instantiateViewControllerWithIdentifier:#"convoView"];
convoVC.convo = newConvo;
NSLog(#"%#", [convoVC.convo memberListToString]);
[self presentViewController:convoVC
animated:YES
completion:nil];
}
Thank you for any help!
I would do what you're trying to do, by embedding the ContactsViewController in its own navigation controller, and segueing (from the Done button) to the new instance of ConvoViewController (the one with the title of "Contacts Detail"). In that controller I have a bar button item called "Main Table" that's connected to an unwind segue that unwinds back to MasterViewController. Here is the setup,
I have a working application which I am just trying to enhance. It's a simple Table View controller which has a plus button; the user clicks that and is modally presented with an Add Entry View Controller which contains a UIDatePicker and 4 text fields.
Right now, the text fields behave normally; you click on them and the keyboard comes up.
I am enhancing my application though to be so that a user taps TextField 1 and is taken modally to a new TableViewController, where they'd essentially be able to Create new entries or select existing ones.
If the user clicks textField 2, they'd go modally to the Table View Controller for that text field (which is different to the first Table View).
How would I go about doing this?
Right now, I have added in a modal segue in the story board for the first text field and when clicking that text field in the running application, the keyboard appears. However, as soon as I dismiss the keyboard, the new table view controller gets modally presented. I want to eliminate the keyboard appearing and show just the table view modally.
Attempting the code below meant I could not modally go to any table view or not pull up the keyboard for any text field because it's not distinguishing between text fields.
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField
{
return NO;
}
Any assistance on differentiating between the text fields to call modally different table views would be really helpful.
Thanks
What i understand is, need to present(push) the UIViewControllers(TableViews) on already the presented(modally) viewController, If so
First you have to present your modal view controller inside a
navigation controller:
EmailIDValidationController *obj = [self.storyboard instantiateViewControllerWithIdentifier:#"EmailIDValidataion"];
UINavigationController *nc = [[UINavigationController alloc] initWithRootViewController:obj];
[self presentViewController:nc animated:YES completion:Nil];
Then inside the presented VIewController, you can do this:
presentedViewController *obj = [self.storyboard instantiateViewControllerWithIdentifier:#"STORYBARDID"];
[self.navigationController pushViewController:obj animated:YES];
You need to get the tableViewControllers by the UITextField events. So Use the delegates of it;
-(void)textFieldDidBeginEditing:(UITextField *)textField{
presentedViewController *obj = [self.storyboard instantiateViewControllerWithIdentifier:#"STORYBARDID"];
[self.navigationController pushViewController:obj animated:YES];
}
A simple fix.
Creating the segue in the Storyboard (as Modal) and applying textFieldDidBeginEditing instead of shouldBegin, I was able to set the tag and hide the keyboard. This did the trick by calling the new VC.
I'm converting an iPhone app to be a universal app, and it's been mostly straight forward to convert the nested tables into a UISplitViewController arrangement, but I have a remaining issue when running on an iPad that is giving me a headache.
For universal app compatibility, the 'master' view contains a UINavigationController that is used to navigate through a series of TableViews that each displays a menu. This works fine.
Eventually, the user arrives at content that is displayed in the detail view. Each detail view 'chain' is contained in a UINavigationController, as some views can drill down to show maps etc. The idea is that the popover button will live at the root level of the detail view. It's probably important to note that the detail views are created from scratch every time that row is selected.
I've studied Apple's Multiple Detail View Sample Code , and so use the master view as the UISplitViewController delegate, which provides the hide/show popover selectors, and then passes the calls down to whichever substitute detail view is selected.
When working in landscape mode, I can select different rows in the master view, and the detail views switch nicely - everything works great. It's wonderful.
In portrait mode, things don't work quite so well... the popover button displays correctly in the currently selected detail view when rotating to portrait, but then disappears when a row is selected (i.e. it's somehow not being added correctly to the newly selected view's NavBar).
I've added diagnostic code, and it looks like the correct calls (with correct pointers) are being made to show the popover button on the newly selected detail view. Also, I can rotate to landscape and back again, and the popover button then appears so I'm reasonably satisfied that the popover UIBarButtonItem is being hooked up to the new detail NavBar correctly.
As the detail views are not created until the row is selected, I was wondering if this was a case of the UINavigationBar not being instantiated at the time that showRootPopoverButtonItem is called (based on Apple's sample code). This theory is supported by the fact that the popover button appears if I rotate to landscape and back again (as mentioned above) with the same view selected.
I also see this comment in Apple's sample code, in didSelectRowAtIndexPath, and just before switching the detail views, note the use of the word 'after'...
// Configure the new view controller's popover button (after the view has been displayed and its toolbar/navigation bar has been created).
So, I tried calling the showRootPopoverButton method again in viewWillAppear (by which time the UINavigationBar should exist), but that doesn't cause the popover button to appear either.
I'd appreciate any thoughts and suggestions as to how to get the popover button to appear immediately when a new row is selected from the master view when in portrait mode. Thanks.
Thanks for reading this far, the relevant code is below.
From the master view, here are the UISplitViewControllerDelegate selectors,
- (void)splitViewController:(UISplitViewController*)svc willHideViewController:(UIViewController *)aViewController withBarButtonItem:(UIBarButtonItem*)barButtonItem forPopoverController:(UIPopoverController*)pc
{
// Keep references to the popover controller and the popover button, and tell the detail view controller to show the button.
barButtonItem.title = #"Root View Controller";
self.popoverController = pc;
self.rootPopoverButtonItem = barButtonItem;
//UIViewController <SubstitutableDetailViewController> *detailViewController = [self.splitViewController.viewControllers objectAtIndex:1];
// ^ Apple's example, commented out, my equivalent code to obtain
// active detail navigation controller below,
UINavigationController *detailNavController = [self.splitViewController.viewControllers objectAtIndex:1];
UIViewController *detailViewController = detailNavController.visibleViewController;
[detailViewController showRootPopoverButtonItem:rootPopoverButtonItem];
}
- (void)splitViewController:(UISplitViewController*)svc willShowViewController:(UIViewController *)aViewController invalidatingBarButtonItem:(UIBarButtonItem *)barButtonItem
{
// Nil out references to the popover controller and the popover button, and tell the detail view controller to hide the button.
UINavigationController *detailNavController = [self.splitViewController.viewControllers objectAtIndex:1];
UIViewController *detailViewController = detailNavController.visibleViewController;
[detailViewController invalidateRootPopoverButtonItem:rootPopoverButtonItem];
self.popoverController = nil;
self.rootPopoverButtonItem = nil;
}
And, very much like Apple's example, here's what happens when a row is selected in the master table,
if (rootPopoverButtonItem != nil)
{
NSLog (#"show popover button");
[newDetailViewController showRootPopoverButtonItem:self.rootPopoverButtonItem];
}
From the detail view,
- (void)showRootPopoverButtonItem:(UIBarButtonItem *)barButtonItem
{
NSLog (#"detailViewController (view: %p, button: %p, nav: %p): showRootPopoverButton", self, barButtonItem, self.navigationItem);
barButtonItem.title = self.navigationItem.title;
[self.navigationItem setLeftBarButtonItem:barButtonItem animated:NO];
popoverButton = barButtonItem;
}
- (void)invalidateRootPopoverButtonItem:(UIBarButtonItem *)barButtonItem
{
NSLog (#"detailViewController (%p): invalidateRootPopoverButton", self);
// Called when the view is shown again in the split view, invalidating the button and popover controller.
[self.navigationItem setLeftBarButtonItem:nil animated:NO];
popoverButton = nil;
}
There are two things that I think could be the problem here. You should include the rest of your code. Specifically the part where you change the detail view controller when the user performs an action in the master.
visibleViewController may be nil if you just instantiated detailNavController. Even if you set it's root, there is no "visible" view controller since it actually hasn't displayed yet. You may want to try using topViewController
I'm not sure if you're creating a new detailNavController every time the user selects something in the master but if you are, you need to pass the rootPopoverButtonItem into the detailViewController again because - (void)splitViewController: willHideViewController: withBarButtonItem: forPopoverController: only gets called automatically when the orientation changes.