I'm trying to do very simple example of a UINavigationController. Here is my code:
- (void)viewDidLoad {
[super viewDidLoad];
This next line works, or at least doesn't blow up.
navController = [[UINavigationController alloc] initWithRootViewController:self];
self.title = #"blah";
PageOneController *one = [[[PageOneController alloc]init] autorelease];
Example 1. THIS LINE DOES NOTHING
[navController pushViewController:one animated:NO];
Example 2. THIS LINE WORKS (but no nav controller, of course)
[self.view addSubview:one.view];
}
Why am I unable to push ViewController instances onto the navController and see the screen change?
Note: I realize that I might have my concepts backwards and I don't need to have my view referencing a UINavigationController... or something.
- (void)viewDidLoad {
[super viewDidLoad];
PageOneController *one = [[[PageOneController alloc]init] autorelease];
one.title = #"blah";
navController = [[UINavigationController alloc] initWithRootViewController:one];
[self.view addSubview:navController.view];
}
The basic idea behind it is that a navigation controller's root view controller is the controller which view will be displayed first in the navigation controller hierarchy. The root controller is not the view controller that you plug the navigation controller into. Hope this helps.
I'm just restating #E-ploko's answer, which is 100% correct (which is why I marked it best answer).
You need more views (and view controllers) to use the UINavigationController. One of them houses the UINavigationController, and its rootViewController is the first page of the series (the one that has no "back").
I got rid of the external dependencies for the code sample: obviously this is monolithic sample code, not monolithic real code.
- (void)viewDidLoad {
[super viewDidLoad];
UIViewController *one = [[UIViewController alloc] init];
[one.view setBackgroundColor:[UIColor yellowColor]];
[one setTitle:#"One"];
navController = [[UINavigationController alloc] initWithRootViewController:one];
// here 's the key to the whole thing: we're adding the navController's view to the
// self.view, NOT the one.view! So one would be the home page of the app (or something)
[self.view addSubview:navController.view];
// one gets reassigned. Not my clearest example ;)
one = [[UIViewController alloc] init];
[one.view setBackgroundColor:[UIColor blueColor]];
[one setTitle:#"Two"];
// subsequent views get pushed, pulled, prodded, etc.
[navController pushViewController:one animated:YES];
}
Related
I created an onscreen tutorial for my iOS app.
To accomplish this I'm using a UIPageViewController which is managing 3 viewControllers.
- (void)setupContentViews{
UIViewController *screenTutorial1 = [[UIViewController alloc] initWithNibName:#"ScreenTutorial_1ViewController" bundle:nil];
UIViewController *screenTutorial2 = [[UIViewController alloc] initWithNibName:#"ScreenTutorial_2ViewController" bundle:nil];
tutorialPages = #[screenTutorial1, screenTutorial2];
}
Everything works great, except that when I got to change the background for screenTutorial1 or screenTutorial2 it never gets called. What's the reason for this? Is there a solution?
- (void)viewDidLoad
{
[super viewDidLoad];
self.view.backgroundColor = [UIColor redColor];
NSLog(#"line 30)");
}
After some experimentation it appears that if I add the code in UIPageViewController (see below) it sets the property. But what if I need to add any custom methods to my View Controllers? Do I need to do everything from UIPageViewController?
The problem is that at that point the view is nil so you can't change the backgroundColor.
You should subclass UIViewController and set the backgroundColor in viewDidLoad. And after that you should initialize the view controllers for the tutorialPages ivar like this:
YourUIViewControllerSubclass *screenTutorial1 = [[YourUIViewControllerSubclass alloc] initWithNimbName:#"ScreenTutorial_1ViewController" bunble:nil];
Update
Update your method setupContentViews like this:
- (void)setupContentViews
{
ScreenTutorial_1ViewController *screenTutorial1 = [[ScreenTutorial_1ViewController alloc] initWithNibName:#"ScreenTutorial_1ViewController" bundle:nil];
UIViewController *screenTutorial2 = [[UIViewController alloc] initWithNibName:#"ScreenTutorial_2ViewController" bundle:nil];
tutorialPages = #[screenTutorial1, screenTutorial2];
}
I don't know if what I'm trying to do is possible, but because I haven't the desired results, I guess not.
What I'm trying and need to do is to call a SplitViewController from a previous ViewController, using presentViewController.
I know, SplitViewController have to be the rootViewController, but I need to explore the most possible options to achieve what I need to do.
I have a MainMenu with buttons, and with every button, I need to call a SplitViewController. First, how can do this?
What I'm trying to do is this:
First, in AppDelegate I'm calling the MainMenu, and add as a subview and other things:
-(BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[window addSubview:self.mainMenu.view];
[self.mainMenu presentModalViewController:self.firstMenu animated:NO];
[self.window makeKeyAndVisible];
return YES;
}
Then, in the MainMenu, I'm calling SecondViewController, in modal view, using this code:
SecondViewController *secV = [[SecondViewController alloc]initWithNibName:#"SecondViewController" bundle:nil];
secV.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentModalViewController:secV animated:YES];
In this SecondViewController, I'm creating SplitViewController, with Master & DetailViewController's, using this code:
-(void)viewDidLoad{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
UISplitViewController *splitViewController = [[UISplitViewController alloc]init];
SecondMenuViewController *secMenu = [[SecondMenuViewController alloc]init];
UINavigationController *navLef = [[UINavigationController alloc]init];
[navLef pushViewController:secMenu animated:NO];
SecondMainViewController *secMain = [[SecondMainViewController alloc]init];
UINavigationController *navRig = [[UINavigationController alloc]init];
[navRig pushViewController:secMain animated:NO];
splitViewController.delegate = secMain;
splitViewController.viewControllers = [NSArray arrayWithObjects:navLef, navRig, nil];
MainAppDelegate *mainApp = [[MainAppDelegate alloc]init];
[mainApp changeRootViewController:splitViewController];
navRig = nil;
navLef = nil;
secMain = nil;
secMenu = nil;
splitViewController = nil;
}
As you can see, I'm calling a method in MainAppDelegate, to change view and RootViewController, because SplitViewController have to be RootViewController. This is the method:
-(void)changeRootViewController:(UISplitViewController *)splitViewController{
[self.window addSubview:splitViewController.view];
self.window.rootViewController = splitViewController;
}
I know, this looks like a mess. And when I run, the SplitViewController never shows, so I assume, what I'm trying to do is not possible? Or In what I'm wrong?
If it is everything, what can I do to show a SplitViewController after my MainViewController?
I'm using XCode4.4 and iOS5
Thank you very much
A better way would be to make your UISplitViewController the root view controller in application:didFinishLaunchingWithOptions:. Then present your MainMenu on top of it. You can change the subviews displayed by the split view controller to correspond to what button the user pushes in your MainMenu.
First, didFinishLaunchingWithOptions: is too early to be calling presentModalViewController. You haven't even got an interface yet!
Second, you don't seem to have a root view controller (although perhaps you're getting one from a nib? you should probably stop doing that; use the techniques shown in the current application templates).
Third, note that now that we have custom container views, there is no need for you to use UISplitViewController at all; you can construct your own view / view controller hierarchy, and you might be happier doing so, since UISplitViewController is not a very well-constructed class.
I have the following code for the UITabbarcontroller:
NSMutableArray *arr = [[NSMutableArray alloc] init];
tabBarController = [[UITabBarController alloc] init];
FirstViewController *firstview = [[FirstViewController alloc] init];
[tabBarControllerViews addObject:firstview];
[firstview release];
SecondViewController *secondview = [[SecondViewController alloc] init];
[tabBarControllerViews addObject:secondview];
[secondview release];
[tabBarController setViewControllers:arr animated:YES];
[arr release];
self.view = tabBarController.view;
This code runs fine on IOS4. I tried it on IOS5 beta and get the following error when tapping on a UITabbarItem:
*** Terminating app due to uncaught exception 'UIViewControllerHierarchyInconsistency',
reason: 'child view controller:<FirstViewController: 0x6e03be0> should have parent view
controller:<MainViewController: 0x6816d20> but actual parent is:<UITabBarController: 0x6b0c110>'
replace:
self.view = tabBarController.view;
with:
[self.view addSubview:tabBarController.view];
This will also be backwards compatible with IOS3&4.
Had the same pattern (and problem) in my code. Joe's solution didn't work for me. Looking at the snippet, I'm guessing that you derive a class from UIViewController to allow you to customize something.
The thing to do here, and it is quite simple, is to derive from UITabBarController rather than UIViewController, don't create tabBarController, and anywhere you reference tabBarController, substitute self.
5 minutes and you're no longer throwing the inconsistency exception and you remain backwards compatible with iOS 4. You can still do all of your customization in your derived class (monkeying with the nav controller, etc).
If you have built a complex derivation of UIViewController you need to use, this could be more work.
One small gotcha - if you override LoadView, you'll find that it gets called during the init for the UITabBarController. Makes it hard to set members prior to LoadView, so you may need to split up your initialization.
Good luck!
You cannot push or present a UITabbarViewController. Is your First View Controller a UITabBarController ?
I struggled with the same problem.
When you create a new Master-Detail Application(without story board), you can see this codes below from AppDelegate.m.
MasterViewController *masterViewController = [[MasterViewController alloc] initWithNibName:#"MasterViewController" bundle:nil];
self.navigationController = [[UINavigationController alloc] initWithRootViewController:masterViewController];
self.window.rootViewController = self.navigationController;
[self.window makeKeyAndVisible];
"BE NOT DEPENDENT ON MainWindow"
Just start from your own ViewController and set it to delegate.
And don't forget to unlink view from MainWindow.xib else the view will called 2 times.
In my AppDelegate, I create a UITabBarController and UINavigationController dynamically. I then add 3 views to it.
// Prepare the tab bar controller
tabBarController = [[UITabBarController alloc] init];
// Switch controller
UserSettingsController *settingsController = [[UserSettingsController alloc] init];
// Switches controller
SwitchesController *switchesController = [[SwitchesController alloc] init];
// Help controller
HelpController *helpController = [[HelpController alloc] init];
NSArray *controllers = [NSArray arrayWithObjects: switchesController, settingsController, helpController, nil];
tabBarController.viewControllers = controllers;
if (self.navigationController == nil) {
self.navigationController = [[UINavigationController alloc] initWithRootViewController:tabBarController];
}
[window addSubview:navigationController.view];
When I initially did this, I noticed that I now have a header/title bar at the top of my page. It's blank and really just takes up space. I'd like to utilize it though and add a "refresh" button to one of my views.
In the view I'm interested in, I attempted the following:
-(void)viewDidLoad {
[super viewDidLoad];
UIBarButtonItem *refresh = [[UIBarButtonItem alloc] initWithTitle:#"Refresh" style:UIBarButtonItemStylePlain target:self action:#selector(refreshSwitches:)];
self.navigationItem.rightBarButtonItem = refresh;
[refresh release];
}
-(void)refresh{
...
}
No button showed up, so I'm either way off or I'm missing something.
Any suggestions?
Thanks
I believe the problem is that your view controllers that you've added to the tabbar controller are not encapsulated by navigation controllers. Although your UITabBarController has a navigation controller, this does not implicitly give all of its tabs navigation controllers (and without a nav controller, a view controller's navigationItem won't do anything.) To remedy this problem I'd suggest encapsulating your view controllers with navigation controller upon initialization:
ex:
UserSettingsController *settingsController = [[UserSettingsController alloc] init];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:settingsController];
[settingsController release];
// etc...
NSArray *controllers = [NSArray arrayWithObjects:navController, ..., ..., nil];
tabBarController.viewControllers = controllers;
// Release your nav controllers, other cleanup
EDIT
Okay, I think I have a better idea of your setup now. I was able to get the following to work:
self.tabBarController.navigationItem.rightBarButtonItem = myButton;
An important caveat of this is that the button will persist on all of your tabbarcontroller's views unless you explicitly remove it. This may prove annoying/inelegant to maintain, but I am not sure of another solution if the view you want to have the button is not directly associated to a nav controller itself.
What is the better code to move from "page" to "page"?I have a questionnaire on 4 pages and I loading 4 views from 4 xibs.
I picked up 2 way of moving from xib to xib (in my case, from page to page).
Method 1:
-(IBAction) MaleTapped: (id) sender {
Page1M *ivc = [[Page1M alloc] init];
UINavigationController *nc = [[UINavigationController alloc]
initWithRootViewController:ivc];
[self presentModalViewController:nc animated:NO];
[ivc release];
[nc release];
}
Second way:
-(IBAction)GotoPage2M:(id)sender {
page2M = [ [Page2M alloc]
initWithNibName:#"Page2M" bundle:nil];
[self.view addSubview:page2M.view];}
One method uses the RootViewController method, the second just loads the subview. For my 4 pages, which is the better/cleaner/smarter way?
I would recommend using a UINavigationViewController in this way. Going several modal views deep is icky.
- (IBAction) goToNextPage:(id)sender {
UIViewController * newView = [[MyViewController alloc] initWithNibName:#"MyViewController" bundle:nil];
[self.navigationController pushViewController:newView animated:YES];
[newView release];
}
The only reason I might do subviews is for the extra transition options.
I would recommend checking out Apple's sample Page Control code. It shows how to create something that pages through multiple view controllers and load them dynamically from xibs. The example just loads the same xib several times, but you could replace it with code that loads a different view controller or xib for each page.