How to 'pop' to a view contoller not on the stack - objective-c

I have a navigation controller (XCode4 with Storyboards and ARC) where a segue is connected to each individual ViewController (7 of them). Normally, I tap a row in the Nav contoller which takes me to the correct scene. However, there are times that I want to go from scene "A" to scene "C" using segues, then from "C" to "B", which has NOT been placed on the stack by going through the Nav Controller.
Is this somehow possible (to go from scene "C" to scene "B")?
UPDATE: this is the code to put controller on the stack:
EnterDataViewController *edvc = [[EnterDataViewController alloc]init];
NSMutableArray *ma = self.navigationController.viewControllers;
[ma insertObject:edvc atIndex:1];
self.navigationController.viewControllers = ma;
[self.navigationController popToViewController:[self.navigationController.viewControllers objectAtIndex: 1] animated:YES];

Insert the new view controller below the top one in the hierarchy, with animation off. Then, pop. :)

The stack is an array stored in the UINavigationController's viewControllers property. Use it to create a new (mutable) array where you could just insertObject:atIndex:1 and assign this back to the UINavigationController.viewControllers.

Inserting new controllers to the self.navigationController.viewControllers doesn't always work as expected, so I'd recommend to fetch controllers that you need from the array, build a new array from scratch, and then assign this array to navigation controller.
UIViewController *mainScreenController = [[self.navigationController viewControllers]objectAtIndex:0];
SecondScreenController *secondScreenController = [[SecondScreenController alloc]init];
NSMutableArray *controllers = [[NSMutableArray alloc]initWithObjects:mainScreenController, secondScreenController, nil];
[self.navigationController setViewControllers:controllers animated:YES];

Related

Cant pop view controller

I have 2 views, TableController and WirelessController. While in TableController I need to pop the WirelessController view. This is what I've tried and nothing happens, no console output either.
WirelessController *wCon = [[WirelessController alloc] init];
[[wCon navigationController] popViewControllerAnimated:YES];
and this has the same problem.
[self navigationController] popViewControllerAnimated:YES];
Is it the fact that I'm using the UINavigationController when it's a view based app?
I think you mean to push, not pop...
WirelessController *wCon = [[WirelessController alloc] init];
[[self navigationController] pushViewControllerAnimated:YES];
Push adds a new item to the top of the stack; pop removes the top item from the stack.
Update
From your comments it seems that...
your first view is an instance of WirelessController.
from there you present a TableController modally
now you want to return to you wirelessController.
In this case you need to send a message back to the presenting view controller (wirelessController) asking it to dismiss the view controller it has presented (tableController)
In tableController:
[self presentingViewController] dismissViewControllerAnimated:YES
completion:nil]];
Whatever is going on, you certainly don't want to do this:
WirelessController *wCon = [[WirelessController alloc] init];
This line will create a new object. You want to return to an existing object.
Pushing and popping viewControllers is an activity generally associated with navigation controllers, which keep an array of managed viewControllers. In that case you would push to add a new controller to the top of the stack, and pop to remove it from the stack. In the absence of a navigation controller, there is no such stack, so push and pop make no sense.

NavigationController Back Skip View

First off, I am aware of this question being asked in a forward manner, but in this case I am asking for a backwards manner in which the Navigation Controller is already designed. With that being said...
I have a UINavigationController with three views: Table, Get, and Avail in that order that was created in IB.
When going forward, I want to go from Table to Get to Avail, but when I hit the "Back" button on Avail I want to skip over Get and go directly back to Table. Is this possible? If so, how?
Here's how I did it:
NSArray *VCs = [self.navigationController viewControllers];
[self.navigationController popToViewController:[VCs objectAtIndex:([VCs count] - 2)] animated:YES];
To be able to override the nav controllers's back button you're going to have to subclass UINavigationController. Check out how in this tutorial: http://www.hanspinckaers.com/custom-action-on-back-button-uinavigationcontroller
Implement the navigation controller's delegate method:
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated
It will fire for all VCs in the nav controller stack. (put this code in the VC immediately after your navigation controller, and put < UINavigationControllerDelegate > in the .h)
What you want to do is replace the entire Navigation Controller's stack without the view controller you want to remove in it. Since it's a non-mutable array, you have to convert it to a mutable array before removing the VC, remove the VC, then replace the navigation controller's stack with the call [self.navigationController setViewControllers:newVCs animated:NO];. That's the crucial part. You are replacing the stack after you have loaded the page you are on but since you're keeping the VC you're on, it's still the top item on the stack so there is no visible impact to the user. As long as you don't have a lot of VCs on the stack, it's not an expensive call.
Here's how I did it in the delegate method:
//Remove list setup page if new list was created
if ([self.navigationController topViewController].class == [ItemViewController class])
{
NSArray *VCs = [self.navigationController viewControllers];
if(((UITableViewController*)[VCs objectAtIndex:[VCs count]-2]).class == [NewCardTypeController class])
{
NewCardTypeController *removedObject = [VCs objectAtIndex:[VCs count]-2];
if(removedObject != nil)
{
NSMutableArray *newArray = [NSMutableArray arrayWithArray:VCs];
[newArray removeObject:removedObject];
NSArray *newVCs = [NSArray arrayWithArray:newArray];
[self.navigationController setViewControllers:newVCs animated:NO];
}
}
}
Take a look at UINavigationController's -popToViewController:animated: and -popToRootViewControllerAnimated:, which do exactly what you're asking for. That is, they pop the navigation stack back to a particular view controller, or to the root view controller. You'll still need to intercept the nav controller's back button action to use them, though.

Push another view controller into a UITabBarController view

For the navigation in my app I'm using a UITabBarController. This works fine, but in one of my viewcontrollers I want to push another view controller into the tabbar view.
In other words I want to replace the selected viewcontroller with another one.
I'm doing this with the following code:
self.tabBarController.selectedViewController = self.otherViewController;
The list of viewControllers in my TabBarController does not contain the otherViewController.
This trick works fine in IOS 4.3, but IOS 5 does not like it.
Does anyone know a solution which is accepted by IOS 5?
You want to REPLACE that view controller in the tabbar with another view Controller?
If so, you have to edit the viewControllers property in the tabbar by setting a new one. It would be something like:
UIViewController *thisIsTheViewControllerIWantToSetNow;
int indexForViewControllerYouWantToReplace;
NSMutableArray *tabbarViewControllers = [self.tabbar.viewControllers mutableCopy];
[tabbarViewControllers replaceObjectAtIndex:indexForViewControllerYouWantToReplace withObject:thisIsTheViewControllerIWantToSetNow];
self.tabbar.viewControllers = tabbarViewControllers;
[tabbarViewControllers release];
You can't just use a navigation controller or similar on this tab?
Anyway this should work:
NSMutableArray *controllers = [NSMutableArray arrayWithArray:rootTabBarController.viewControllers];
[controllers replaceObjectAtIndex:rootTabBarController.selectedIndex withObject: newController];
rootTabBarController.viewControllers = controllers;

UISpliviewcontroller in recursive calls fail after a memory warning

Any help is appreciated ! It's several days I'm fighting w/o results.
The scenario:
I and iPad application have a SplitViewController that shows 2 controllersViews (Root on the left e Detail on the right)
The Root allows a recursive navigation (tree that could be several drilldown levels) and I'm calling every time the same controller class (UITableView) pushing always in the controller stack). When the user taps a cell (left side), the detail view (right side) shows the information.
Keep in mind that the detail view controller is not always the same class: it means that I'm allocating (and releasing) programmatically several detailView controllers according the kind of information I have to display.
Here the fragment:
UIViewController <ItemGenericViewController> *newDetailViewController = [[NSClassFromString(cntrClass) alloc] initWithNibName:cntrXib bundle:nil];
//the detailViewController has been defined in the head section as ItemGenericViewController
//each detailViewController is a subclass of ItemGenericViewController
detailViewController = newDetailViewController;
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:detailViewController];
// Update the split view controller's view controllers array.
NSArray *viewControllers = [[NSArray alloc] initWithObjects:self.navigationController, nav, nil];
self.splitViewController.viewControllers = viewControllers;
[nav release];
[viewControllers release];
[detailViewController release];
Everything is working fine until a memory warning arises.
From that moment if I try to display a new detailViewcontroller the "connection" in the SplitViewController, between the RootController and the detailController, seems vanished. The result is: nothing appear on the right part of the splitController.
In the mean time if I navigate to parent level in the root controller the situation still failing.
For your information each time I push in the stack a new RootController instance (left column) I'm releasing the same controller (to save memory as usual) and I suspect, after receiving the memory warning, iOS is trying to free itself memory and my "history" disappear and the related connection, throught the split controller, too.
Is a nightmare ;-)
Do you have any suggestion ?
Thanks
Dario
I had a similar problem to you (maybe even worse - 16 combinations of possible view switches)... But I believe i have solved it right now.
So, i believe you have used Apple's example for view switching (I have, with modifications), and if you have so, problem is that "root" splitViewController (from MainWindow.xib) get's "niled" as default behavior when memory warning. And even if you add new array of view controllers to it, it will not cause any change (and even worse, it will not show any sign of warning). And solution is to check is it nil, and if is, to reinitialize it.
here is the code, using example from above:
UIViewController <ItemGenericViewController> *newDetailViewController = [[NSClassFromString(cntrClass) alloc] initWithNibName:cntrXib bundle:nil];
//the detailViewController has been defined in the head section as ItemGenericViewController
//each detailViewController is a subclass of ItemGenericViewController
detailViewController = newDetailViewController;
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:detailViewController];
// Update the split view controller's view controllers array.
NSArray *viewControllers = [[NSArray alloc] initWithObjects:self.navigationController, nav, nil];
/**** Milos Edit ****/
if (self.splitViewController == nil) {
// I'm keeping reference in app delegate, but any way to reinitialize splitViewController is OK
self.splitViewController = delegate.splitViewController;
}
/**** end of edit ****/
self.splitViewController.viewControllers = viewControllers;
[nav release];
[viewControllers release];
[detailViewController release];
Hope it will be helpful.
Cheers
Milos

Using setViewController from UINavigationController on iPhone doesn't behave properly

I'm having a problem with an iPhone App using UINavigationController. When I'm using pushNavigationController, it works fine. The iPhone does its animation while switching to the next ViewController. But when using an array of ViewControllers and the setViewControllers method, it has a glitch in the animation which can grow into a clearly visible animation bug.
The following snippet is called in the root ViewController. Depending on a condition it should either switch to ViewController1, or it should directly go to ViewController2. In the latter case the user can navigate back to vc1, then to the root.
NSMutableArray* viewControllers = [NSMutableArray arrayWithCapacity:2];
// put us on the stack
[viewControllers addObject:self];
// add first VC
AuthentificationViewController* authentificationViewController =
[[[AuthentificationViewController alloc] initWithNibName:#"AuthentificationViewController" bundle:nil] autorelease];
[viewControllers addObject:authentificationViewController];
if (someCondition == YES)
{
UserAssignmentsListViewController* userAssignmentsListViewController =
[[[UserAssignmentsListViewController alloc] initWithNibName:#"UserAssignmentsOverviewViewController" bundle:nil] autorelease];
[viewControllers addObject:userAssignmentsListViewController];
}
[self.navigationController
setViewControllers:[NSArray arrayWithArray:viewControllers] animated:YES];
As you can see I'll add the first and maybe the second VC to the array, finally setting the navigationController stack with animation. This works properly if I only add the first controller. But in the case where the animation should go to the 2nd controller, the navigation bar's title won't be "flying in". Instead there is an empty title until the animation is finished. And, even worse, if I replace the navbar title with a custom button, this button will be displayed in the upper left corner until the animation is finished. That's quite a displaying bug.
I tried to use a workaround with multiple pushViewController methods, but the animation doesn't look / feel right. I want the navigation to do its animation in the same way as pushViewController does. The only difference here is, that I don't add a VC but set the whole stack at once. Is there another workaround here, or could this be considered as a framework's bug? I thought about using only pushNavController for VC2, then somehow insert VC1 into the stack, but that doesn't seem possible.
Thanks for all hints and advices. :-)
Technical data: I'm using iOS 4.2, compiling for 4.0.
Finally I found the solution. The mistake was that the new top-level NavigationController has not been initialized and loaded properly until the animation is done. In my case, UserAssignmentsListViewController has a viewDidLoad method that will not be called until animation is done, but it sets the navigation title (here: a UIButton). Therefore the animation fails.
The solution is to refer to an already initialized view controller when it comes to pushing it to the stack. So initialize our top-level VC somewhere:
// initialize our top-level controller
ViewController* viewController2 = [[[ViewController alloc]
initWithNibName:#"ViewController" bundle:nil] autorelease];
Then when pushing two or more VCs to the stack, the top level one is already initialized and the animation works (following the example from my original question):
NSMutableArray* viewControllers = [NSMutableArray arrayWithCapacity:2];
// put us on the stack, too
[viewControllers addObject:self];
ViewController* viewController1 = [[[ViewController alloc]
initWithNibName:#"ViewController" bundle:nil] autorelease];
[viewControllers addObject:viewController1];
if (someCondition == YES)
{
[viewControllers addObject:viewController2];
}
[self.navigationController
setViewControllers:[NSArray arrayWithArray:viewControllers] animated:YES];