is there a way to present a view controller already loaded? - ios

i have an app with 5 views, mostly consisting of drill downs.
lets say i drill down to the 4th view controller. is there a way to present the second view controller exactly as it is without recreating it and modally present that view?
the drill downs do a sort of round about and i dont want to force the user to reselect their selection on the first view to bring them into the second view
so its like this (tvc = tableviewcontroller)
tvc1 > tvc2 > tvc3 > tvc4 > tvc2 > tvc5
^ ^
these two views are the same view in memory

You cannot present tvc2 again while it is already in the "stack" of presented view controllers.
If you push your view controllers onto a navigation controller's stack, then you can change the order of the view controllers in that stack by assigning to its viewControllers property or by sending it setViewControllers:animated:. You can hide its navigation bar if you don't want users to see it. I don't think it's safe to put the same view controller into the stack in two places at the same time.

You can possibly iterate over the visible viewControllers, and use the is casting operator to check if it is that type of class. Then find the view controller and pop to it.
Objective-C
for (id viewController in self.navigationController.viewControllers) {
if ([viewController isKindOfClass:[ViewControllerClass class]]) {
[self.navigationController popToViewController:viewController animated:TRUE];
}
}
Swift 2.0
for viewController in self.navigationController!.viewControllers {
if viewController is ViewControllerClass {
self.navigationController?.popToViewController(viewController, animated: true)
}
}

UINavigationController has an array called viewControllers. This will give you the list of UIViewControllers that exist in that navigation stack.
You can try something like:
UIViewController *yourTableViewController = [self.navigationController.viewControllers objectAtIndex:(theIndexOfYourTableViewController)];
Hope this helps.

Related

popToRootViewControllerAnimated returning NSArray of "popped" VCs, but not calling viewWillDisappear

I'm currently working on a project which implements a custom navigation controller, whose code is here:
https://gist.github.com/emilevictor/724a6602fedb8100650c
In one of my controllers, which gets pushed to the navigationController via a push segue, I have an action on a button to return to the main screen:
- (IBAction)returnToMainScreen:(id)sender
{
NSArray *returnedControllers = [self.navigationController popToRootViewControllerAnimated:YES];
NSLog(#"Popped to root view controller.");
}
This will return the current view controller and one preceding it in the returnedControllers array.
However, it doesn't change screens, or call any viewDidDisappear functions. Anyone know why?
Make sure you child viewControllers are being added to the parent viewController with the method [UIViewController addChildViewController:]

removeFromParentViewController removes all the view controller from the stack

In my app, I want to remove a view controller from the stack of view controllers and then navigate to a particular view controller. Say I have A,B,C,D,E view controller stack. I wish to remove E and then go to C. I use the following code
NSMutableArray *allViewControllers = [NSMutableArray arrayWithArray:[self.navigationController viewControllers]];
for (UIViewController *aViewController in allViewControllers)
{
if ([aViewController isKindOfClass:[noNetworkViewController class]])
{
[aViewController removeFromParentViewController];
}
}
NSMutableArray *allbViewControllers = [NSMutableArray arrayWithArray:[self.navigationController viewControllers]];
for (UIViewController *aViewController in allbViewControllers)
{
if ([aViewController isKindOfClass:[messageViewController class]])
{
[self.navigationController popToViewController:aViewController animated:NO];
}
}
The first for loop erases all the view controllers from the stack. Is there something I am doing wrong here. Please let me know.
You should not be mucking around with a navigation controller's stack of view controllers using removeFromParentViewController:. Ever. That is flat-out wrong.
Furthermore, there is no reason to loop through a navigation controller's stack of view controllers and popping them one by one. There are methods like popToViewController:animated: and popToRootViewController that let you pop to any arbitrary view controller in the stack with one call.
Have you verified that the parent view controller property is populated by the navigation controller? The parent view controller is normally used for UIViewControllerContainment protocol conformance. UINavigationController does not conform to that protocol, which makes me think you're not going to be able to remove it from the navigation controller by calling that method. You can use – setViewControllers:animated: on your navigationController to assign the controllers. Just use the array you created with all the viewControllers.
If you are using a pop, you don't have to remove the other view from it's parent view. You can just pop to a view controller that is in the view controller array and back two from the top of the stack.
I would give a code example, but i'm not on my dev computer. If nobody has answered with more detail before tomorrow, I will add some code.
-Edit - Here is the line of code that I use to pop back 2 view controllers. I'm sure this could be done better ways, but it works.
[self.navigationController popToViewController:[self.navigationController.viewControllers objectAtIndex:[self.navigationController.viewControllers count]-3] animated:YES];

How can I present a 'standalone' View Controller ('forgetting' the previous one)

I have a regular UINavigationController with a couple of views attached, which are working perfectly fine. Its RootViewController has a custom Menu-button on the top left, at the same place as the "Back"-button is on the attached views. When clicking this menu-button, the menu appears and presents five options.
Obviously, by clicking one of these option, you would be presented with the ViewController for that option.
I want to completely 'forget' the current ViewController, and move on to this new controller. Usually, I would do something like [self presentViewController....]; or [self.navigationController push..];, but in these methods the current ViewController will, I think, always exists 'below' the new presenting viewController (as you would return to this instantiation if using [self dismissViewController..];, I don't want this).
In the presenting ViewController there will be a menu-option to return back to the original controller, but I still want this to be a clean instantiation of it, and not just popping. By thinking ahead in time, I figured I would potentially create an infinite number of ViewControllers on top of each other by using the methods I know of this way.
I entered the world of iOS after the era of ARC began, so I have no clue how to release or deallocate such views, which I assume has relevance here.
The second View Controller is also supposed to be a root in a UINavigationController, and I'm not sure if it's best to use the same UINavigationController, or if I should present a new one, and dismiss the old. Essentially, I would like to replace the Navigation Controller's rootViewController from the rootViewController, but I don't see how that would be possible. Or possibly push to ViewController2, and then popping the rootViewController out of the hierarchy, leaving the new ViewController as the root, but then I assume I'd have problems with the navigational back-button(if it's even possible).
I figured it's just as easy to let ViewController2 be root at its own NavigationController, and presenting this NavigationController from ViewController1. The problem is, I want to completely remove everything that has to do with ViewController1 and its NavigationController from memory after presenting ViewController2, but I have no idea how.
I'm open to other solutions to my situation, but I'd also like an answer to how I can completely 'forget' a view after presenting another on general basis.
If you want to "forget" controllers, you can just replace the window's root view controller with a new one. The original one will be deallocated if you don't have any other strong pointers to it. I'm not sure I understand all of what you're trying to do, but for example, if you want controller 1 and controller 2 to both be root view controllers of a navigation controller, and you don't want controller 1 around when you switch to 2, then do something like this from controller one:
SecondViewController *second = [[SecondViewController alloc] initWithNibName:#"SecondViewController" bundle:nil];
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:second];
self.view.window.rootViewController = nav;
This will switch out the controllers, and controller 1 and its navigation controller will be deallocated (assuming that the only thing with a strong pointer to the navigation controller was the window, through its rootViewController property).
Create a menu UIViewController and add it as a root to the UINavigationController on launch. Add 1st UIViewController as a child controller to menu UIViewController when viewDidLoad of menu controller is called. When you click menu to show 2nd UIViewController, remove the 1st UIViewController from menu view controller and add 2nd UIViewController to child of the menu view controller. You can put NSLog in both, 1st and 2nd view controller's dealloc method to check if its released or not. Logic is like this
//inside menuvc class
#interface MenuVC{
UIViewController * currentVC; // current child controller to menuVC
}
-(void)viewDidLoad{
[self addChildController:firstVC]; //to add view controller 1 intially
currentVC = firstVC;
}
-(void)add2ndChildController{
[currentVC removeFromParentViewController];
[self addChildController:secondVC]; //to add view controller 2 when needed
currentVC = secondVC;
}
// dealloc of 1st vc
-(void)dealloc{
NSLog(#"first vc released");
}
I just wrote some sample logic of what I explained before, you have to generalize this logic if you feel its right for you. Hope it helps :)

iOS + Storyboard: Re-using ViewControllers in navigation

in my current application, I want to use a certain UICollectionView several times, but with different selection behaviours. Consider the following storyboard layout as "is" (and working):
Tab Bar Controller (2 items):
-> Navigation Controller 1 -> Collection View Controller -> some Table View Controller
-> Navigation Controller 2 -> (Basic) View Controller
The Basic View Controller has two UIButtons which have Storyboard Push-connections to the Collection View Controller. What I want is to transition from the Basic View Controller to the Collection View Controller, but selecting an item from the collection should pop the view and return to the Basic View Controller.
I have set a custom property in the Collection View Controller which gets set in the corresponding prepareForSegue message of the Basic View Controller (or not at all, if the user selects a Tab Bar Item), so there's no problem in detecting which controller or which UI component triggered the push (there are 3 ways: selecting the tab bar item or tapping one of the buttons on Basic View).
The problem is popping the Collection View.
My code so far:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ( self.mode == nil ) {
// do nothing
} else if ( [self.mode isEqualToString:#"foobar"] ) {
// one way I tried
[self dismissViewControllerAnimated:YES completion:nil];
} else if ( [self.mode isEqualToString:#"blah"] ) {
// other method
BasicViewController *targetVC = self.navigationController.viewControllers[ 0 ];
[self.navigationController popToViewController:targetVC animated:YES];
}
}
Unfortunately, my app crashes in the lines dismiss resp. popToViewController. Is it even possible to use the same view controllers in different ways of navigation?
I hope it's enough information to help me out on this one. As you might know, projects grow, and I don't know if there's more code to consider :)
Thanks in advance!
The prepareForSegue: method is not the right place to put that code. It's called right before the segue is performed, which usually means there's allready some kind of transition about to happen.
I'm assuming you've connected your collection view cells with a segue and are now trying to modify the behaviour of that segue depending on the viewcontroller 'before' the collectionVC.
In that case there are some possible solutions:
don't use segues for that specific transition and do any vc-transitions manually
write your own UIStoryboardSegue subclass that can handle the different transitions. See UIStoryboardSegue Class Reference. Then you can set some property of your custom segue in the prepareForSegue: method, to tell the segue which transition it should perform.
use unwinding segues. See What are Unwind segues for and how to use them?
The action of a selected a row in your 'Collection View Controller' differs depending on from where the 'Collection View Controller' was presented. In one case, return to 'Basic View Controller' and in the other segue to 'some Table View Controller'
Implement this by defining tableView:didSelectRowAtIndexPath: with something like:
- (void) tableView: (UITableView *) tableView
didSelectRowAtIndexPath: (NSIndexPath *) indexPath
{
if (self.parentIsBasicViewController) // set in the incoming segue, probably.
[self.presentingviewController dismissViewControllerAnimacted: YES completion: nil]
else
[self performSegueWithIdentifier: #"some Table View Controller" sender: self];
}

Interface Builder IB - Navigation Controller, skipping a view

Hello I am new to iOS and have a question about navigating through my views.
I am using IB wiring up the PREVIOUS and NEXT buttons in the nav bar that pushes my views. This all works fine. However I am having trouble finding out where exactly what I need to do or where I need to place the code so I can skip over a view. To simplify my situation...
-I have 1 Nav Controller and 4 View Controllers named VC1, VC2, VC3, VC4. Each VC has a .H/.M
-Starting from VC1, they follow one after the other.
Say I want to skip VC3 and jump right to VC4 based on a setting in VC2. Where would I put the code to do this? Would I need to unhook the IBAction method from the NAV buttons at VC3?
I do apologize if this has been covered before. If there is a tut or if you know of a post that answers this, please let me know. I did do a search but the search was returning generic posts probably due to me using the wrong terminology.
Thanks in advance.
A couple of thoughts:
If you want to push from VC2 to either VC3 or to VC4, rather than having VC3 immediately push to VC4 in special cases, I think it's better to just have VC2 push directly to the appropriate view controller. Thus you might have an IBAction in VC2 that would do this for you:
- (IBAction)pushToNext:(id)sender
{
BOOL skipToVC4 = ... // put in whatever logic you'd use to bypass VC3 and go directly to VC4
UIViewController *nextController;
if (skipToVC4)
{
nextController = [self.storyboard instantiateViewControllerWithIdentifier:#"VC4"];
// obviously, if using NIBs, you'd do something like:
// nextController = [[ViewController4 alloc] initWithNibName:#"VC4" bundle:nil];
}
else
{
nextController = [self.storyboard instantiateViewControllerWithIdentifier:#"VC3"];
}
[self.navigationController pushViewController:nextController animated:YES];
}
This way, when you pop back from VC4, you'll pop back directly to the appropriate view controller (e.g. if you pushed from VC2 to VC4, when you pop, you'll pop right back to VC2 automatically.
And, obviously, if you're using storyboards, rather than manually invoking pushViewController, you could have two segues from VC2 (one to VC3 and one to VC4), give them appropriate identifiers, and then just invoke performSegueWithIdentifier to segue to the appropriate view controller. But the idea is the same: You can define an IBAction that performs the appropriate segue depending upon whatever logic you so choose.
You say that you have "PREVIOUS and NEXT buttons in the nav bar that pushes my views", I wonder about your "PREVIOUS" button. Is that doing a popViewControllerAnimated? Generally, a "NEXT" button will push to a new view controller, but the "PREVIOUS" button should not push to the previous view, but pop back to it. If you don't pop back, you can end up with multiple instances of some of your prior view controllers. Thus, the "PREVIOUS" button should be linked to an IBOutlet that does something like:
- (IBAction)popToPrevious:(id)sender
{
[self.navigationController popViewControllerAnimated:YES];
}
When popping back, you'll obviously pop back to the view controller that you pushed from. If you want to skip a few of the view controllers as you're popping back in iOS versions prior to 6.0, you would use popToViewController or popToRootViewControllerAnimated. For example, let's say that you pushed from VC1 to VC2, to VC3, to VC4. If you want to pop back from VC4 all the way to VC1, you would hook up and IBAction in VC4 like:
- (IBAction)popToRoot:(id)sender
{
[self.navigationController popToRootViewControllerAnimated:YES];
}
Or, if you wanted to pop back from VC4 to VC2, you would
- (IBAction)popToVC2:(id)sender
{
for (UIViewController *controller in self.navigationController.viewControllers)
{
if ([controller isKindOfClass:[ViewController2 class]])
{
[self.navigationController popToViewController:controller animated:YES];
return;
}
}
}
You can avoid this iteration through the navigationController.viewControllers if you passed a reference of VC2 to VC3 and then again to VC4, but sometimes the above technique is easier.
By the way, if you're supporting iOS 6 and above, only, and are using storyboards, you can also use unwind segues, which are a more elegant way of popping back to a particular view controller. But it's not clear whether (a) you're using storyboards; and (b) you're supporting iOS 6 and above only, so I'll refrain from a discussion of unwind segues at this point.
First:
Show us some code, where you are pushing new view controllers, maybe your whole navigation controller code
Second (Solution):
I assume:
Your prev/next buttons are linked to your navigationController class
you have the appropriate methods (prevPressed:/nextPressed:), which are called, when you click one of the buttons
I can help you with the following:
you know which controller is visible at the moment with the visibleViewController #property
each time you click on a button in the navBar you can ask the visibleViewController which next/previous view controller should be pushed/popped
Best solution would be, if all of your controllers VC1/2/3/4 are a subclass of a viewController class, which defines a method in it's interface:
- (Class)nextViewControllerClass;
- (Class)previousViewControllerClass;
and in the implementation:
- (Class)nextViewControllerClass {
return [VC4 class];
}
- (Class)previousViewControllerClass {
return [VC1 class];
}
And in your navigationController code the do this:
- (IBAction)next:(id)sender {
UIViewController *nextViewController = [[[self.visibleViewController nextViewControllerClass] alloc] init];
[self pushViewController:nextViewController animated:YES];
}

Resources