I'm pushing a number of views:
the top one is a UITabBarController
the second one is a UINavigationController with a pushed view
the third one is a modal box.
Once the close button in the modalbox is pressed I'm trying to revert everything to the default state and change the tabbar index.
[self dismissModalViewControllerAnimated:YES];
[self.navigationController popViewControllerAnimated:NO];
[self.tabBarController setSelectedIndex:3];
This dismisses the modal view but doesn't do anything else. Any ideas what could be wrong? I read something about a possible ios bug but I don't know how to work around it.
Neither UITabBarController nor UINavigationController is a view. Both are subclasses of UIViewController and have a property NSArray *viewControllers.
If you have an actualView controlled by an ActualViewController that is pushed on top of a rootView controlled by a RootViewController that is the rootViewController for the navigationController, and you also have a modalView controlled by a ModalViewController, then put
[self dismissModalViewControllerAnimated:YES];
in ModalViewController.m, and put
[self.navigationController popViewControllerAnimated:NO];
in ActualViewController.m (from whence modalView is pushed, presumably), and put
[self.tabBarController setSelectedIndex:3];
in RootViewController.m (from whence actualView is pushed, presumably).
If modalViewController was never added to the navigationController, then it doesn't know that the navigationController exists.
If actualViewController was never added to the tabBarController, then it doesn't know that the tabBarController exists.
The easy (and dirty) way:
Dismiss the modal view in the modal view. Make the navigation view controller the delegate of the modal view. Make the tabbar controller the delegate of the navigation controller. When the button is pressed call a method in the navigation controller that pops the view and calls a method of the tabbar controller which changes the selected tab.
Related
I have seen this topic (iOS Storyboard Back Button) and more about this subject, but I still cannot have my back button appear on screen :
I have got 2 viewControllers, the two of them have a navigationController, the "father" controller has the button bar item set to "back" as a plain text, the second viewController appear well with a modal segue, or with "show detail (replace)" segue, but nothing appear on the navigation bar to come back... Would you know why?
Here is a capture :
Thanks
EDIT :
with a custom transition, and when presenting the controller via the navigator in the code, the back button is not here anymore... would someone know why?
When I comment out presentViewController:secondViewController, the back button is here, but the custom animation is not triggered anymore, there is the normal transition set in the storyboard.
Here is my method :
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
NSLog(#"segue descr %# : ", [[sender class] description] );
if ( [[segue identifier] isEqualToString:#"second"] ){
//SecondViewController *secondViewController = [[UIStoryboard storyboardWithName:#"Main" bundle:nil] instantiateViewControllerWithIdentifier:#"SecondViewController"];
SecondViewController *secondViewController = [segue destinationViewController];
secondViewController.transitioningDelegate = self;
[self.navigationController presentViewController:secondViewController
animated:YES
completion:nil];
Modal and show detail segues don't use a back button, because they are like independent views.
The idea is that those those views are only showing extra information or details about something and you can close them programmatically when you need to go back to the previous view.
A show or push segue will give you the back button in your navigation controller, because that segue is meant to be more like a sequence of views.
When you push a View Controller, you get the back button for "free" without having to write extra code. When you present a modal View Controller, you need to add your own way of dismissing the modal view. Since you have a Navigation Controller, your easiest route is probably to add a UIBarButtonItem to the navigation bar, and have that bar button call dismissViewControllerAnimated:completion: in your UIViewController subclass.
I created a new navigation controller in my storyboard (not programmatically!) and set it to be "Root View Controller" to a regular UIViewController and added a button in it which says- forward to the next view controller (this second view controller is a view controller which I want that will have a back button to link to the initial view controller).
Now, whenever I try to link the button to the next view controller- "Pushing a navigation controller is not supported".
Help me please, and thanks
EDIT:
I accidentally subclassed UINavigationController, and not UIViewController in my class.
Thank you anyway.
I've tried this and have no problems, its all done in IB with no additional code required ...
Start a new project, "Single View Application" using story boards
Select storyboard and delete the views its produced.
Drag on a new Navigation Controller (it will bring a table view with it)
Delete the table and the table view controller, so you are just left with the Navigation Controller
Drag on a normal view controller
Right Click and drag from the Navigation controller to the new View and choose "Relationship - Root View Controller"
Drag a "Bar Button Item" on to the Navbar which should be visible on the top of your ViewController, you can rename this Forward if you wish.
Now drag on another view controller which is the one your "Forward" button will push in to view.
Right Click and drag from the bar button to the 2nd View Controller, and choose "Push"
Run the project and you will get a Single view with a Navbar and your button, clicking your button will Push the other view and give you a Back Button to return to the first View Controller. I'll try and post a picture of my storyboard if it helps.
Plasma
I had the same trouble. I wanted to have a navigation controller on each storyboard, so that each could run independently, be individually debugged, and so that the look would be right with the navigation bar.
Using other approaches, I found the UINavigationController would be retained from the original storyboard -- which I didn't want -- or I'd get errors.
Using the AppDelegate in your view controller to set the rootViewController worked for me (borrowing segue naming conventions from Segue to another storyboard?):
- (void)showStartupNavigationController {
NSLog(#"-- Loading storyboard --");
//Get the storyboard from the main bundle.
UIStoryboard *storyBoard = [UIStoryboard storyboardWithName:#"Startup" bundle:nil];
//The navigation controller, not the view controller, is marked as the initial scene.
UINavigationController *theInitialViewController = [storyBoard instantiateInitialViewController];
NSLog(#"-- Loading storyboard -- Nav controller: %#", theInitialViewController);
//Remove the current navigation controller.
[self.navigationController.view removeFromSuperview];
UIWindow *window = [(AppDelegate *)[[UIApplication sharedApplication] delegate] window];
window.rootViewController = theInitialViewController;
To swap views Programatically you would need to select the segue and give it an Identifier like "PushView" then call it like this ....
[self performSegueWithIdentifier:#"PushView" sender:self];
That will programatically do the same as clicking the forward button. I've created you an example project with the code discussed above. Has an -(IBAction) with code in you can use for programatially changing the view.
PushView.zip
I also wanted to do this, present a screen (that had an embedded navigation controller) when the user pushes a button.
At my first attempt, I connected the segue from the button in the fist screen to the Navigation Controller, and the app was crashing with this error "Pushing a navigation controller is not supported".
This is the solution I found:
Select the segue from the button in the first screen to the navigation controller.
If it had an identifier, copy its name. Then delete that segue.
Then create a new segue by CTRL-clicking the button in the first view controller and dragging to the VIEW CONTROLLER YOU WANT TO PRESENT (not to the Navigation Controller that is pointing at it), and select Push in the small pop up window.
Then click the icon in the middle of the segue and paste the name you copied in the first step as an identifier for it.
IB is going to give you a warning "Scene is unreachable due to lack of entry points and does not have an identifier for runtime access via -instantiateViewControllerWithIdentifier:." Don't worry, it works perfectly.
If you want to customize the string that is shown as the Back button to return, you can add this line in the viewDidLoad method OF THE VIEW CONTROLLER THAT IS BEING SHOWED AFTER THE BUTTON IS PRESSED, that is the Child view controller.
(replace "Settings" with the name you need)
- (void)viewDidLoad
{
[super viewDidLoad];
self.navigationController.navigationBar.topItem.title = #"Settings";
...
}
The start of the structure is as follows...
UITabBarController -> UINavigationController(s)
From each of the UINavigationControllers, I have a UIBarButtonItem that modally presents a UIViewController.
This UIViewController has a MKMapView with pins at multiple locations. When clicked, they display an annotation with a disclosure button.
Within this UIViewController, it is my intention to push a detail page (UITableViewController) when pressing the disclosure button of the annotation. The method calloutAccessoryControlTapped: receives the appropriate pin, but the transition to the next controller fails.
I have tried every combination of the following methods...
[self.navigationController ...]
[self.parentViewController ...]
[self.parentViewController.navigationController ...]
with the method being either...
presentModalViewController:
pushViewController:
I have done all of these with the UIViewController being on its own, and also with it embedded inside of a UINavigationController.
All of these properties return null...
self.navigationController
self.parentViewController
self.parentViewController.navigationController
This is the first time I've used storyboard for an Xcode project. Am I missing a step?
Try getting rid of the code and implementing the transitions in storyboard by control dragging from the button to the view controller you wish to load modally. When the "Storyboard Segue" menu pops up select "modal". In the modal view controller, I like to use code to return from the modal by calling:
[self dismissModalViewControllerAnimated:YES];
To Presenting Storyboard View Controllers Programmatically scroll to that section in gravityjack on the link provided.
For example, I have a view controller that I created in storyboard which I can call programmatically with the following two statements:
SettingsViewController *settingsVC = [self.storyboard instantiateViewControllerWithIdentifier:#"settingsVC"];
[self.navigationController pushViewController:settingsVC animated:YES];
I'm implementing a Passcode feature in my iPhone app which has a UITabBarController as a root view controller. I have everything working great in most situations, by displaying a modal Passcode ViewController from the tabBarController when the app goes into the background, like so:
- (void)applicationDidEnterBackground:(UIApplication *)application
{
if ([[NSUserDefaults standardUserDefaults] valueForKey:kPasscodeStringKey]) {
PasscodeEntryVC *passcodeView = [[PasscodeEntryVC alloc] init];
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:passcodeView];
[tabBarController presentModalViewController:nav animated:NO];
}
}
My problem comes when the app is already displaying a modal view controller when it enters the background. Then, no passcode view appears. What would be the correct way to do this? Instead of just sending the message to the tabBarController to present the view, should I be checking first to see what the current view is, then have that present the passcode? If so, how is this done? Thanks.
First - you are leaking memory because you do not release your passcodeView and navigation controller nav.
Second - you could keep a simple BOOL variable that is updated whenever a modal view is presented or dismissed. If there is a modal view, just call dismissModalViewController:animated: in your applicationDidEnterBackground: method.
You could also check the frontmost view controller with [self.navigationController.topViewController class], but I have found this to be unreliable.
What I usually do is to ensure that any views I have that may present a modal view controller to dismiss the modal view controller whenever it is sent the UIApplicationWillResignActiveNotification notification, while over in my app delegate, I set it up exactly like yours.
One caveat though, is that whenever you dismiss the said modal view controllers, you need to ensure that you dismiss them with animated: set to NO before presenting your passcode view controller.
I have an app delegate, whose default view should be preceeded by a modal view controller, and sometimes by two modal view controllers. So in the app delegate's didFinishLaunchingWithOptions, I'm checking if there is need for, and in that case displays, the first modal view controller.
Upon dismissing the first modal view controller (using [self dismissModalViewControllerAnimated:YES];), I may want to display the second modal view controller. This is known by the app delegate as well.
So my solution was to use NSNotificationCenter to tell the app delegate that the first modal view controller now have been dismissed. When that happens, the second modal view controller can be displayed by the app delegate, if it is needed.
It works fine, but is there a cleaner solution? I think NSNotificationCenter is really ugly stuff.
Note on displaying multiple modal view controllers at once
I did try to display the first AND the second modal view controller inside of didFinishLaunchingWithOptions, but I never got it working. Here's what I tried:
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[window setRootViewController:tabBarController];
[self.window makeKeyAndVisible];
[tabBarController presentModalViewController:pinViewController animated:NO];
if([self needsActivation]) {
[tabBarController presentModalViewController:activationViewController
animated:YES];
}
}
UPDATE: The above code works with the following correction:
if([self needsActivation]) {
[pinViewController presentModalViewController:activationViewController
animated:YES];
}
In this particular case, there was no need for NSNotificationCenter, as I thought. I had tried to display multiple modal view controllers, but I'd made an error.
When displaying the modal view controller B from the modal view controller A, it works fine. I had tried presenting modal view controller A and B from a parent view controller.
So when presenting modal view controllers in a hierarchy instead, there is no need for NSNotificationCenter. The view controllers are dismissing themselves, animations works and I'm a step further towards bending the UIKit to my will.
I've edited the code in my question, which now works fine.