I have seen a bunch of topics where multiple ViewControllers go to one single ViewController, but not the opposite.
I am making a game where you select a game from Game_select.m and it needs to go out to one of 6 View Controllers. I've tried using storyboard and hardcoding it but neither have worked for me.
I have already imported Game1.h and Game2.h into Game_select.m.
When I run my code it always goes to Game1 ViewController.
This is code I am trying:
if(getGame1) {
//go to game1
Game1 *game1 = [[Game1 alloc] init];
[self.navigationController pushViewController:game1 animated:YES];
}
if(getGame2) {
//go to game2
Game2 *game2 = [[Game2 alloc] init];
[self.navigationController pushViewController:game2 animated:YES];
}
Thanks for the help in advanced.
Cheers.
Personally I wouldn't use navigation controllers for this case. Make all of your viewControllers subclasses of (the normal) UIViewController, then use this code to present one of your viewControllers:
Note that this code will only work if you are setting up your view programatically or if using xib's (not storyboard) then this will also work if you use initWithNibName: bundle: instead of using init
if(getGame1) {
//go to game1
Game1 *game1 = [[Game1 alloc] init];
[self presentViewController:game1 animated:YES completion:nil];
}
if(getGame2) {
//go to game2
Game2 *game2 = [[Game2 alloc] init];
[self presentViewController:game2 animated:YES completion:nil];
}
Create manual segues from your main viewController to the other viewControllers. First, click on the viewController that you want to be displayed. Make sure you have the viewController itself selected, not one of the views:
Then click on the segues tab (on the far right) and drag from the "Manual Segue" circle to the viewController you want to segue from.
Then click on the segues and give them different names in the tab like this:
Then, in your code, you will have a line like this:
[self performSegueWithIdentifier:#"showAlternate" sender:nil];
You would use that to show the viewController for the "showAlternate" identifier. You will have multiple segues with identifiers like "Game1" and Game2".
Double check that getGame1 and getGame2 are BOOL values and not some sort of object that will evaluate to YES if its non-nil. I have made this mistake many times:
NSNumber* booleanValue = #NO;
if (booleanValue) {
// this code will run
}
if ([booleanValue boolValue]) {
// this code will not run - as expected
}
Seriously, I've made this mistake tons of times.
Related
I have two xib files. I start with "ViewController.xib" and when the user clicks a button it loads a different xib file named "GamePage.xib".
The code of the button is:
-(IBAction)startButtonClicked{
UIViewController *gamePage = [[UIViewController alloc] initWithNibName:#"GamePage" bundle:nil];
[self presentViewController:gamePage animated:NO completion:nil];
}
When the button is clicked, the second xib is shown on screen, but its "viewDidLoad" method don't run…
Why is that?
UIViewController *gamePage = [[UIViewController alloc] initWithNibName:#"GamePage" bundle:nil];
This does not seem correct.
viewDidLoad is probably running but it is viewDidLoad of Apple's UIViewController. You should use your class instead of UIViewController when you initialize it. In general you should use the class which viewDidLoad you need called.
Your XIB must have its Owner. You need to create a class derived from UIViewController and then you need to make that class owner of your XIB.
Just follow these steps:
Open you project
Select the Project Name in the Left Navigation Panel
Right Click and select New File
Select Objective C Type Class
When you click on it, it will ask you to give it a name
Suppose you gave GameViewController
Check the "With XIB for Interface"
and then add the files
Now in your Navigation Panel you will see three classes
GameViewController.h
GameViewController.m
GameViewController.xib
Create your interfaces in your xib.
And now if you have to move to this class and show its view, write the following code:
-(IBAction)startButtonClicked{
GameViewController *gamePage = [[GameViewController alloc] initWithNibName:#"GameViewController" bundle:nil];
[[self navigationController] pushVIewController:gamePage animated:YES];
[gamePage release]; //If not used ARC
}
I have built a program without a menu. The main view has custom buttons which loads XIBs depending on which button is pressed. The only issue i'm having is when I go back to an already used view, its reset. I have tried "retain" in viewDidUnload and in viewWillUnload. I have tried everything I can thin of and cant get it to work.
- (IBAction)gotoMusicView:(id)sender{
//[self.view addSubview:musicview];
if(self.musicMenuData == nil)
{
musicMenu *musicview = [[musicMenu alloc]initWithNibName:#"musicMenu" bundle:[NSBundle mainBundle]];
self.musicMenuData = musicview;
musicview.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self presentModalViewController:musicview animated:YES];
[musicview release];
}
musicMenu *musicview = [[musicMenu alloc] initWithNibName:nil bundle:nil];
musicview.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self presentModalViewController:musicview animated:YES];
}
That musicview loads fine. After I leave that view and go back to the main view, it is RELEASED. Therefore when I reopen "musicview" everything , such as my UIWebView, has reset as if I am opening it again for the 1st time
Create a singleton class using this
And then initialize your .xibs there so you can use the same instance everytime you load the .xib. This way it'll never reset
Instead of allocating and initializing each time you need to use it, keep a reference of all the view controllers you're using, check of its nil on the first time if so initialize, example below.
if(self.someViewController == nil)
self.someViewController = [[ViewController alloc] initWithNibName:#"ViewController"....
//Now present self.someViewController..
Do this for the view controllers that you use for the tabs.
I have a simple app that have 3 views, HomeView, MenuView and GameView.
In the HomeView I have 2 buttons (Menu and Start Game). When the menu button is clicked, I open the MenuView using the following code:
- (IBAction)displayMenu:(id)sender{
MenuView *mv = [[MenuView alloc] init];
[self.view addSubView:[mv view];
[mv release];
}
In the MenuView, I have a button that will allow the user to return to the HomeView. When this button is clicked, I use the following code to return to the HomeView
- (IBAction)returnToHome:(id)sender{
HomeView* hv = [[HomeView alloc] init];
[self.view addSubView:[hv view];
[hv release];
}
The above code is working but is this the correct way of doing it? I was under the impression that when I call the addSubView, the view will be retain so If keep going back and forth between HomeView and MenuView, will i have multiple instance of HomeView and MenuView retained since I keep calling addSubView from each of the view?
Thank you.
You could use the UINavigationController, which will allow you to push UIViewControllers on to the stack.
Using the UINavigationController you will get an nice naviagtionbar in at the top of you screen and the back button.
You can find a nice example here:http://developer.apple.com/library/ios/#documentation/uikit/reference/UINavigationController_Class/Reference/Reference.html
I found this way the most useful and convenient. When calling the new view use this:
HomeView* hv = [[HomeView alloc] init];
(here you can add a uninavigation controller)
[self presentModalViewController:hv animated:YES];
Then to dismiss this view and go back use this:
[self dismissModalViewControllerAnimated:YES];
#atbebtg:
There is a way to do that, infact there are several, since there not really is a "right way" to do it.
For me this works well:
[[self navigationController] setNavigationBarHidden:YES animated:NO];
This will hide the Navigation Bar, so the user can't go back to the last screen.
The other thing you could do is to create your own subclass of UIViewController and not support the button event, like this:
- (IBAction)done:(id)sender
{
//inform the user, that going back is not possible, for example with UIAlertView
//[self.delegate infoViewDidFinish:self];
}
However, this solution seems a bit odd, because the user expects a existing button to work.
Still, this would work.
Others have given answers that present modal view controllers or build a navigation stack. In most cases I would use one of these approaches. Yet, the simplest way to fix the code in the question is to just remove the menu view from the super view. Something like this:
- (IBAction)returnToHome:(id)sender{
[self.view removeFromSuperview];
}
Hi there I am currently testing how to develop an application that has several tableviews with their own parent/child structures that are accessed from a main menu.
I would like to know how to generate the new tableview with the uinavigationcontroller menu but a shared uitabbar as this is the first time I have tried anything like this normally I just stick with apples templates.
Here is a general acitecture of what I am wanting to achieve, any comments suggestions code example would be greatly appreciated and from there I can work through it myself, its more of a question as to where the heck do I start :)
So far I have the main window set up with a UIAction catching the different button clicks, I need to figure out how to allow all children to share the specific UITabbar and then how to set up so that individual branches have their own UINavigationController menu if needed.
this is my UIAction
//Delegate.m
//--- Caputer button clicks ---
- (IBAction)buttonClick: (UIButton *) sender
{
if ([sender isEqual:orangeButton]) {
NSLog(#"orangeButton Pressed");
}
else if ([sender isEqual:blueButton]) {
NSLog(#"blueButton Pressed");
}
else if ([sender isEqual:greenButton]) {
NSLog(#"greenButton Pressed");
}
else if ([sender isEqual:purpuleButton]) {
NSLog(#"purpleButton Pressed");
}
}
If you created a "TabBar based application", then adding the navigationController for the desired tab is pretty easy. Drag the "UINavigationController" inside the "Tab Bar Controller" in the Main Window:
As to "How to generate tableViews", the simplest way, is to create a generic TableView, call it LevelTableView.h/.m file (and it could have its own .xib), where you can add whatever you wish, and then, keep creating new ViewControllers, for instance, in a level of this tableView:
- (void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath
{
LevelTableView *anotherLevel = [[LevelTableView alloc] initWith...];
[self.navigationController pushViewController:anotherLevel animated:YES];
[anotherLevel release];
}
The point is, that this "LevelTableView" is created once, but instantiated various time for each level you want to add, the content is the only thing that changes.
I have exactly something like that. What I do is that I created a different class which I call "TabBarController" and where I initialize all the different views that are going to my tabs:
UIViewController *viewController1 = [[FirstTab alloc] initWithNibName:#"FirstTab" bundle:NSBundle.mainBundle];
UINavigationController *firstNavController = [[UINavigationController alloc]initWithRootViewController:viewController1];
UIViewController *viewController2 = [[SecondTab alloc] initWithNibName:#"SecondTab" bundle:NSBundle.mainBundle];
UINavigationController *secondNavController = [[UINavigationController alloc]initWithRootViewController:viewController2];
myTabBarController = [[UITabBarController alloc] init];
myTabBarController.viewControllers = [NSArray arrayWithObjects:firstNavController, secondNavController, nil];
Then when the user clicks on one of the buttons in the main view this is what I do:
- (IBAction)yourAction:(id)sender {
TabBarController *tabBarController1 = [[TabBarController alloc] init];
tabController.selectedIndex = 1; ==> chose which tab you want to show
[[[UIApplication sharedApplication]delegate].window setRootViewController:tabBarController1.myTabBarController];
}
Does the job pretty nicely.
Add the Navigation Controllers into the tabbar controller. Then Hide the NavigationController and according to your schema use toolbars to pop and navigate your views.
I have a tabBarController with two tabs, first of which contains an instance of NavigatorController. The navigatorController is initiated with a custom viewController "peersViewController" that list all the network peers on a tableView. Upon selecting a peer, an instance of "FilesListViewController" (which list files in the c:\ directory) is pushed into the navigationController stack.
In this filesListViewController I have a button to let it navigate to say documents directory. To do this I'd wired the interface to call a gotoDirectory:(NSString*)path method in the rootViewController:
- (void)gotoDirectory:(NSString*)path {
[[self navigationController] popToRootViewControllerAnimated:YES];
NSArray *files = [self getFilesFromPeerAtPath:path];
FilesListViewController *filesVC = [[FilesListViewController alloc] initWithFiles:files];
[[self navigationController] pushViewController:filesVC animated:YES];
[filesVC release];
}
However, when I press that button, the navigationController did pop my view to the root view controller, but then the FilesListViewController that I instantiated did not appear. From the log, I know that the custom initWithFiles method was indeed called and network stuffs did happen to get the file names.
Something else is screwy about this. I tried clicking on the second tab and then click back to the first tab, and huala! the file names I needed are there. It looks like the data and the filesListViewController was indeed pushed into the navigatorController stack, but the display was not refreshed but stuck at the screen of rootViewController (peersViewController).
Am I doing anything wrong?
--Ben.
-- Edited like 15 minutes after posting the question. I'd found a workaround, but it bothers me that pop and then push doesn't work.
- (void)gotoDirectory:(NSString*)path {
PeersListViewController *rootViewController = (PeersListViewController*)[[[self navigationController] viewControllers] objectAtIndex:0];
[[self navigationController] setViewControllers:[NSArray arrayWithObject:rootViewController]];
FilesListViewController *filesVC = [[FilesListViewController alloc] initWithFiles:files];
[[self navigationController] pushViewController:filesVC animated:YES];
[filesVC release];
}
It doesn't seem like the navigationController should be circumvented this way, and I'd probably have to release all the viewControllers that were in the original stack. This does however work on the iphone 3.0 simulator.
If I'm using this code though, how should the memory release be handled? should I get the original NSArray of viewcontrollers and release everything?
The problem and solution to this issue is actually extremely simple.
Calling [self.navigationController popToRootViewControllerAnimated:YES] sets self.navigationController to nil. When you subsequently call [self.navigationController pushViewController:someOtherViewController] you are effectively sending a message to nil, which does nothing.
To workaround, simply set up a local reference to the navigationController and use that instead:
UINavigationController * navigationController = self.navigationController;
[navigationController popToRootViewControllerAnimated:NO];
[navigationController pushViewController:someOtherViewController animated:YES];
As stated by Jason, the popToRootViewController must be performed without animation for this to work correctly.
Thanks go to jpimbert on the Apple forums for pointing this out.
I got a very similar problem (but without using tab).
I got three viewController : main(root), form and result.
when the UINavigationController stack is
"main -> result"
on a btnClick I do a popToRootViewControllerAnimated then a push of the formViewCtrl.
in order to have
"main -> form"
the navbar title and back button label are correct and the formViewCtrl's event are called.
BUT, I still see the main view.
Here is my "solution"
After doing some test, I found out that without the animation to go to the rootViwCtrl this work fine. So I only use the animation to push viewCtrl.
iPhone 3.0, problem found on device & simulator.
If i got something new, i will update/comment my post.
I see that this question about popping to the root and then pushing a new ViewController is pretty prevalent, and this post is viewed a lot, so I wanted to add my bit to help other new guys out, especially those using Xcode 4 and a storyboard.
In Xcode 4, you have a storyboard. Let's say you have these view controllers: HomeViewController, FirstPageViewController, SecondPageViewController. Make sure to click each of them and name their identifiers by going to the Utilities pane->Attributes Inspector. We'll say they're named Home, First, and Second.
You are Home, then you go to First, then you want to be able to go to Second and be able to press the back button to go back to Home. To do this, you want to change your code in FirstPageViewController.
To expand on the example, make a button in FirstPageViewController in the storyboard. Ctrl-drag that button into FirstPageViewController.m. In there, the following code will achieve the desired outcome:
// Remember to add #import "SecondPageViewController.h" at the top
SecondPageViewController *secondView = [self.storyboard instantiateViewContorllerWithIdentifier:#"Second"];
UINavigationController *navigationController = self.navigationController;
NSArray *array = [navigationController viewControllers];
// [array objectAtIndex:0] is the root view controller
NSArray *viewControllersStack = [NSArray arrayWithObjects:[array objectAtIndex:0], secondView, nil];
[navigationController setViewControllers:viewControllersStack animated:YES];
Basically, you're grabbing the view controllers, arranging them in a stack in the order you want, and then having the navigation controller use that stack for navigation. It's an alternative to pushing and popping.
I found a workaround but I cannot explain why it is working:
1. First push the needed controller.
2. Then pop to the one you want to.
This is totally illogical, but it works for my case.
Just to make things clear, I'm using it in the following scenario:
First Screen -> Goes to Loading Screen -> Second Screen
When I'm on the Second Screen, I don't want to have the Loading Screen in the stack and when click back I should go to the First Screen.
Regards,
Vesko Kolev
You can actually keep the "Go back" animation, followed by the "Go forward" animation by basically delaying the push animation till after the pop animation is complete. Here is an example:
(Note: I have an NSString variable called "transitionTo" in my appDelegate that's initially set to #"")...First, set that variable to an NSString you can detect for later. Then, pop the controller to give you a nice screen transition back to the root:
appDelegate.transitionTo = #"Another";
[detailNavigationController popToRootViewControllerAnimated:YES];
Then inside the rootviewcontroller's class, use the viewDidAppear method:
-(void)viewDidAppear:(BOOL)animated
{
AppDelegate *appDelegate =(AppDelegate*) [UIApplication sharedApplication].delegate;
if([appDelegate.transitionTo isEqualToString:#"Another"])
{
[self transitionToAnotherView];
appDelegate.transitionTo = #"";
}
}
-(void)transitionToAnotherView
{
// Create and push new view controller here
AnotherViewController *controller = [[AnotherViewController alloc] init];
UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithTitle:#"Home" style:UIBarButtonItemStyleBordered target:nil action:nil];
[self.navigationItem setBackBarButtonItem:backButton];
[[self navigationController] pushViewController:controller animated:YES];
}
So basically, pop to the root...when the transition finishes at "viewDidAppear"...then push the next view. I happened to keep a variable to tell you which view you wish to transition to (with #"" meaning not to do a transition in the case that I want to stay on this screen).
Nick Street's answer works great if you want to popToRootViewController and subsequently push another VC.
VC1 -> VC2 -> VC3: hit the back button from VC3 => VC2, then VC1, here OK
However, when VC1 pushes VC2, which in turn pushes VC3, then going back to VC1 directly from VC3 does not work as wished:
I've implemented in VC3's -(void)viewWillDisappear:(BOOL)animated:
-(void)viewWillDisappear:(BOOL)animated{
...
[self.navigationController popToRootViewControllerAnimated:YES];
}
I also tried to implement it in the "back button", same result: upon hitting the back button from VC3 to go back to VC1: it breaks. The actual VC is VC1, but the navigation bar is still VC2. Playing with other combinations, I get VC1's navBar on VC2. Total mess.
Loda mentioned something about timing. I think that's the main issue here. I've tried a few things, so maybe I'm missing out something here, but this is what worked for me, at last:
In VC3:
-(void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
// notify VC2
[[NSNotificationCenter defaultCenter] postNotificationName:backFromV3 object:self];
}
In VC2:
-(void)viewDidLoad {
...
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(backFromV3)
name:#"BackFromV3"
object:nil];
}
-(void)backFromV3{
[NSTimer scheduledTimerWithTimeInterval:0.5
target:self
selector:#selector(backToRootViewController)
userInfo:nil
repeats:NO];
}
-(void)backToVC1 {
self.navigationItem.rightBarButtonItem = nil;
[self.navigationController popToRootViewControllerAnimated:YES];
}
Of course, do the necessary cleaning.
The timer is critical here. If 0, it breaks. 0.5 seems to be alright.
That works perfectly for me. A little heavy, but I have not been able to find anything that does the trick.