I want to use a modal view (UIViewController) as a "normal" view, which can be pushed on the navigation controller stack. Normally, a modal view is presented like this:
LoginViewController *myView = [[MyViewController alloc] init];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:myView];
[self.navigationController presentModalViewController:navController animated:YES];
[myView release];
myView = nil;
[navController release];
navController = nil;
But I want to do something like this:
[[self navigationController] pushViewController:myView animated:YES];
The problem is that my modal view has a right and a left button. So I would have to check how the view is loaded and present the buttons in another way. The idea behind this is to have the back button. So I can use the same modal view a few times.
Edit:
#petert:
Now I followed your example. My issue is that I'm using a UINavigationBar for the modal view. To get this UINavigationBar I create a navigation controller. I'm using the navigation bar because I have my buttons in it. So checking if parentViewController is of type UINavigationController does not work for me. I'm always getting a modal view. Here is how I do it:
// load modal view
MyViewController *myView = [[MyViewController alloc] init];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:myView];
[[self navigationController] presentModalViewController:navController animated:YES];
[navController release];
navController = nil;
[myView release];
myView = nil;
// load as normal view
MyViewController *myView = [[MyViewController alloc] init];
[[self navigationController] pushViewController:myView animated:YES];
Good tips in this StackOverflow answer.
I prefer to use UIViewController's property:
#property(nonatomic, readonly) UIViewController *parentViewController
in a view controller's subclass:
Look at the value of the controller's parentViewController property. If it's an instance of UINavigationController, then you're in the navigation stack. If you're being displayed modally, it'll be an instance of your last view controller.
So in -viewDidLoad for example:
- (void)viewDidLoad
{
if ([self.parentViewController isKindOfClass:[UINavigationController class]])
{
// navigation controller
self.title = #"...";
}
else
{
// modal
self.title = #"Modal";
// add cancel and done buttons now...
}
}
Or, a pretty simple solution would be to customize your init method to your MyViewController class to encode your intent for the view controller.
Add the following to the MyViewController header:
#interface MyViewController : UIViewController
{
BOOL modal;
}
- (id)initForModal:(BOOL)isModal;
#end
Now in the implementation file:
#interface MyViewController ()
#property (nonatomic) BOOL modal;
#end
#implementation MyViewController
#synthesize modal;
- (id)initForModal:(BOOL)isModal;
{
if (self = [super initWithNibName:#"MyViewController" bundle:nil])
{
self.modal = isModal;
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
if (self.modal)
{
// add cancel and done buttons …
}
else
{
// assuming we're presented from a navigation view …
}
}
Now to use this modally:
// load modal view
MyViewController *myView = [[MyViewController alloc] initForModal:YES];
Or not modally:
// load as normal view
MyViewController *myView = [[MyViewController alloc] initForModal:NO];
I'm assuming you're creating the view controller(s) from NIBs, but as always see the View Controller Progamming Guide for iOS and especially the section titled "Defining a Custom View Controller Class".
For clarification: myView isn't modal. You just present it as a modal one.
If you just push it into a UINavigationController hierarchy it will behave like a "normal" one.
You can't push the same view controller onto the navigation stack several times. Just once.
Also see this for how to customize the view:
SO modal question
Related
I initiate the following Modal Controller:
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
UIViewController *modal = [storyboard instantiateViewControllerWithIdentifier:#"modalController"];
modal.title = #"Example Title";
[self presentModalViewController:modal animated:YES];
I set the title with:
modal.title = #"Example Title";
but this doesn´t work, can anyone help me?
Edit:
I have wrapped my ModalView with a UINavigationController like this
You are going about this in a slightly convoluted way... but to stay with your paradigm, you need to present the navigationController, not the contained viewController: trying to do it the latter way will instantiate the viewController, but this action will not pull the containing navController along with it out of the storyboard. You are setting the viewController's title property ok, but you have no (automated) way to display the title. Whereas if you instantiate the navController, it's contained viewController does get unarchived along with it as it's topViewController.
//give the navigation controller a storyboard id eg "navVC"
UINavigationController* modalNav = [self.storyboard instantiateViewControllerWithIdentifier:#"navVC"];
[modalNav topViewController].title = #"Example Title";
//[self presentModalViewController:modalNav animated:YES];
//deprecated method, use this instead:
[self presentViewController:modalNavController
animated:YES
completion:nil];
You have to add a IBOutlet property for UINavigationItem (the title) in your model controller with a classic drag & drop method.
#property (weak, nonatomic) IBOutlet UINavigationItem *navTitle;
Then set the title in viewDidLoad function.
- (void)viewDidLoad
{
[super viewDidLoad];
// title view
[self.navTitle setTitle: #"atitle"];
}
I am having issues with the back button not showing up on the SettingsViewController. The navigation bar does show up when the view is pushed, but no back button.
I am creating this inside a view controller, which is not a navigation controller. Any ideas or suggestions on what is actually going on here.
- (void)viewDidLoad
{
self.title = #"Settings";
}
- (IBAction)showSettingsModal:(id)sender
{
SettingsViewController *settingsViewController = [[SettingsViewController alloc] initWithNibName:#"SettingsViewController" bundle:nil];
UINavigationController *navController = [[[UINavigationController alloc] initWithRootViewController:settingsViewController] autorelease];
[self presentModalViewController:navController animated:YES];
[settingsViewController release];
}
You are creating a new navigation stack. You will need to add your own Back button and set the action of that to a delegate method on the calling VC to dismiss it.
UPDATE:
There seems to be lots of confusion about where and how to dismiss ModalViewControllers. The wrong thing to do in most cases is to call the Dismiss method from the Modal VC itself if you are wanting the parent to act on that dismissal. Instead, use delegation. Here is a simple example:
ModalViewController.h:
#protocol ModalViewControllerDelegate
-(void)dismissMyModalVC;
#end
#interface ModalViewController : UIViewController {
id < ModalViewControllerDelegate > delegate;
}
#property (nonatomic, retain) id < ModalViewControllerDelegate > delegate;
// The rest of your class properties, methods here
ModalViewController.m
#synthesize delegate;
...
// Put in the Method you will be calling from that Back button you created
[delegate dismissMyModalVC];
CallingViewController.h:
#import "ModalViewController.h"
#interface CallingViewController : UIViewController
<ModalViewControllerDelegate>
// Rest of class here
CallingViewController.m:
ModalViewController *mvc = [[ModalViewController alloc] initWithNibName:#"ModalViewController" bundle:nil];
mvc.delegate = self
[self presentModalViewController:mvc animated:YES];
...
// The ModalViewController delegate method
-(void)dismissMyModalVC {
// Dismiss the ModalViewController that we instantiated earlier
[self dismissModalViewControllerAnimated:YES];
That way the VC gets dismissed properly from the controller that instantiated it. That delegate method can be modified to pass along objects as well (like when you are finished logging a user in, etc)
SettingsViewController does not have a back button because it is at the bottom of stack. If you want a button to dismiss the modal dialog, you will have to add it yourself.
you can try this
UIBarButtonItem * backButton = [[UIBarButtonItem alloc]initWithTitle:#"Back"style:UIBarButtonItemStylePlain target:self.navigationItem.backBarButtonItem action:#selector(dismissModalViewControllerAnimated:)];
You are presenting your new controller as modal view controller. Modal controller presents its topmost. You should:
[self.navigationController pushViewController:navController animated:YES];
to push view controller onto the stack, and then you will see Back button.
Read Apple documenation on presenting view controllers:
https://developer.apple.com/library/ios/#featuredarticles/ViewControllerPGforiPhoneOS/ModalViewControllers/ModalViewControllers.html
EDIT Didn't see that the calling view controller is not part of the navigation controller. In that case, you will have to create back button manually, and set it as a left bar navigation item.
here's my code:
ViewController *vc = [[ViewController alloc] initWithNibName:#"TableView" bundle:nil];
[self.navigationController presentModalViewController:vc animated:YES];
//[self setView:[vc view]];
If I call it, nothing happens. However, if I change it to:
ViewController *vc = [[ViewController alloc] initWithNibName:#"TableView" bundle:nil];
//[self.navigationController presentModalViewController:vc animated:YES];
[self setView:[vc view]];
The view appears just fine (without the transition, of course).
What am I doing wrong? Is there anything special you have to take care of when initializing the view controller? I tried to copy as much as possible from Apple's examples, but I can't get this to work...
Thanks for any input!
-- Ry
You can only present modal view controllers from controllers that have already been shown onscreen (usually through a UINavigationController or UITabBarController). Try creating a UINavigationController, pushing a viewController to it, and then presenting your modal controller. There's a starter project in Xcode that shows how to create a UINavigationController-based flow if you're unfamiliar with it.
One other thing to note: if you haven't pushed the view controller onto a UINavigationController, the .navigationController property will be nil, and messaging it will have no effect.
I encountered the same problem while attempting to show a modal view over another modal view. Ben's answer is correct, and can be implemented like so:
#interface FirstView: UIViewController {
UIViewController *firstView;
}
- (IBAction)showOptionsView:(id)sender;
#end
In the main view class:
- (void)viewDidLoad {
[super viewDidLoad];
firstView = [[UIViewController alloc]init];
[firstView setView:self.view];
[firstView setModalTransitionStyle:UIModalTransitionStyleCoverVertical];
}
- (IBAction)showOptionsView:(id)sender {
OptionsView *optView = [[OptionsView alloc]initWithNibName:#"OptionsView" bundle:nil];
if(firstView != nil) {
[firstView presentModalViewController:optView animated:YES];
[optView release];
}
Hey everyone, I am new to iPhone development and I'm not understanding the whole UINavigationController and UITabBarController idea. Is one a substitute for the other - how do apps such as Tweetie combine both?
I'd like to have my app have a persistent Tab Bar # the bottom (which seems to be working), but also a Navigation bar at the top which can push/pop views onto the screen without removing the tab bar.
How can I accomplish this?
What should the hierarchy look like in IB as far as my MainWindow.xib with regards to all of these controllers?
What is best practice here?
Thanks very much,
Just wrap the view controller inside the UINavigationController and Place the UINavigationController inside the UITabBar.
This will work fine for you…
Example:
NSMutableArray *tabBarViewControllers = [[NSMutableArray alloc] initWithCapacity:2];
tabBarController = [[UITabBarController alloc] init];
[tabBarController setDelegate:self];
UINavigationController *navigationController = nil;
navigationController = [[UINavigationController alloc] initWithRootViewController:<Your View controller1>];
[tabBarViewControllers addObject:navigationController];
[navigationController release];
navigationController = nil;
navigationController = [[UINavigationController alloc] initWithRootViewController:<Your View controller2>];
[tabBarViewControllers addObject:navigationController];
[navigationController release];
navigationController = nil;
tabBarController = tabBarViewControllers;
[tabBarViewControllers release];
tabBarViewControllers = nil;
Use the wizard for a Tab Bar Application, and set it up as normal. In any tab where you want to add a navigation controller, create it in the XIB using the library. My XIB has:
- File's Owner DescriptiveNameNavViewController
- First Responder
- View UIVIew
- Navigation Controller UINavigationController
- Navigation Bar UINavigationBar
Note that there isn't anything in the view. See viewDidLoad below for where the UINavigationController gets attached to the UIView.
In the header file for the Tab's ViewController (which I've here called DescriptiveNameNavViewController -- there isn't a particular standard for this, but I use [Something]NavViewController to remind me that this ViewController contains a navigation controller with the navigation stack. This is the controller name that I set in the MainWindow.xib that the wizard generates) Set up a UINavigationController * IBOutlet that has the navigation controller in the XIB attached to it:
#interface DescriptiveNameNavViewController : UIViewController {
UINavigationController *navigationController;
}
#property (nonatomic, retain) IBOutlet UINavigationController *navigationController;
#end
In the controller for the DescriptiveNameNavViewController , do something like this:
- (void)viewDidLoad {
[super viewDidLoad];
[[self view] addSubview:[navigationController view]];
DescriptiveNameController *aController = [[[DescriptiveNameController alloc ] initWithNibName:#"DescriptiveNameController" bundle:nil ] autorelease];
aController.title = #"Descriptive Title";
//
// app stuff goes here.
//
[self.navigationController pushViewController:aController animated:YES];
[self.navigationController setDelegate:self];
}
Setting the delegate in the DescriptiveNameNavViewController is super-important, because otherwise you won't get the methods called that you expect in DescriptiveNameViewController instances and anything else you push into the navigation controller's stack.
In DescriptiveNameNavViewController, implement the UINavigationControllerDelegate methods like this:
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
if ([viewController respondsToSelector:#selector(viewDidAppear:)]) {
[viewController viewDidAppear:animated];
}
}
And that will cause messages to get propagated to controllers inside the UINavigationController like you expect. It seems like many problems that people encounter are because the viewDidAppear: or other methods aren't getting called on the ViewControllers pushed into the NavigationController.
Anyway, let me know if more detail would help.
I'd like to launch a modal view controller the way one does with 'ABPeoplePickerNavigationController' and that is without having to creating a navigation controller containing the view controller.
Doing something similar yields a blank screen with no title for the navigation bar and there's no associated nib file loaded for the view even though I am invoking the initWithNibName when the 'init' is called.
My controller looks like:
#interface MyViewController : UINavigationController
#implementation MyViewController
- (id)init {
NSLog(#"MyViewController init invoked");
if (self = [super initWithNibName:#"DetailView" bundle:nil]) {
self.title = #"All Things";
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.title = #"All Things - 2";
}
#end
When using the AB controller, all you do is:
ABPeoplePickerNavigationController *picker = [[ABPeoplePickerNavigationController alloc] init];
picker.peoplePickerDelegate = self;
[self presentModalViewController:picker animated:YES];
[picker release];
ABPeoplePickerNavigationController is declared as:
#interface ABPeoplePickerNavigationController : UINavigationController
The other way to create a modal view as suggested in Apple's 'View Controller Programming Guide for
iPhone OS':
// Create a regular view controller.
MyViewController *modalViewController = [[[MyViewController alloc] initWithNibName:nil bundle:nil] autorelease];
// Create a navigation controller containing the view controller.
UINavigationController *secondNavigationController = [[UINavigationController alloc] initWithRootViewController:modalViewController];
// Present the navigation controller as a modal view controller on top of an existing navigation controller
[self presentModalViewController:secondNavigationController animated:YES];
I can create it this way fine (as long as I change the MyViewController to inherit from UIViewController instead of UINavigationController). What else should I be doing to MyViewController to launch the same way as ABPeoplePickerNavigationController?
I'd like to launch a modal view controller the way one does with 'ABPeoplePickerNavigationController' and that is without having to creating a navigation controller containing the view controller
But this is exactly what ABPeoplePickerNavigationController is doing. It isn't magic, it is a UINavigationController that instantiates a UIViewController internally (a UITableView that is populated with your address book contacts) and sets the UIViewController as its root view.
You can indeed create your own similar UINavigationcontroller subclass. However, within it's initializer, you will need to create a view controller to load as its root view just like ABPeoplePickerNavigationController does.
Then you can do what you are trying like this:
[self presentModalViewController:myCutsomNavigationController animated:YES];
In the code you posted:
#interface MyViewController : UINavigationController
#implementation MyViewController
- (id)init {
NSLog(#"MyViewController init invoked");
if (self = [super initWithNibName:#"DetailView" bundle:nil]) {
self.title = #"All Things";
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.title = #"All Things - 2";
}
#end
I suspect you are having NIB issues. there isn't a "rootViewController" outlet to connect. This is why you have a blank screen.
The initalizer you should be using internally is this:
self = [super initWithRootViewController:myCustomRootViewController];