Hide a VC and segue from previous VC to new VC? - ios

I want to hide the ViewController on top of stack say VC . Then the VC just below this VC must be segue into new VC. And unHide the VC which was previously hidden , to the top of segue stack. How can I achieve this ??
ie , what I am trying to say is :
Segue Stack initial -> VC1 -> VC2
Hide VC2 then and segue from VC1 -> VC3
then show VC2 above it.
Final Segue Stack -> VC1 -> VC3 -> VC2

You can do something like this
Whne in VC2, you can dismiss VC2 and push VC3 to stack like this
self.dismiss(animated: true) {
self.navigationController?.pushViewController(VC3, animated: true)
}
And you can push VC2 when VC3 loads

You can achieve it by manipulating navigationController.viewController property. See how..
For example you want to remove a ViewController from stack you do following..
NSMutableArray *newStack=[[NSMutableArray alloc] init];
for(id controller in self.navigationController.viewControllers){
if(![controller isKindOfClass:[YourViewControllerToRemove class]]){
[newStack addObject:controller];
}
}
Finally assign the array like
self.navigationController.viewControllers=newStack;
The controller not in the array will be removed.
Now when you want to add just use
[self.navigationController pushViewController:<YourViewController> animated:YES];
Note: Check if the removed ViewController deallocs.
Cheers.

How about UINavigationController's method, setViewControllers(:animated:)
let viewList: [UIViewController] = [V1, V2]
self.navigationController?.setViewControllers(viewList, animated: true)
UPDATE
V1, V2 are instances of class. They are created from storyboard or codes. If they are existed in the stack of UINavigationController already, they can be parsed from viewControllers property of UINavigationController.
let list = self.navigationController?.viewControllers
for v in list {
// if v is V1 or V2 ....
}
I wrote a sample project and put it on here: https://app.box.com/file/284901357701.

Related

Swift ios unwind to root ViewController tab from anywhere

I have a tab based app. And I wonder if its possible to unwind the user from anywhere to the root of the current tab.
My app is built like this: TabController -> (tabs) -> navigationController -> vc etc..
So lets say that I press on tab 4 in my app. Now I dig through two viewcontrollers and end up in a modal presented VC. Now I want to go back straight to the root of the tab:
tab 4 -> navigationController -> rootVC -> pushVC -> pushVC -> modalVc
So I eg from modalVc to rootVC
I know that I can do something like this, BUT I want to go to the rootVC from any VC within tab4, and using that technique would force me to create a unwind segue on all of my child VCs in tab 4
I am using notifications and when a user press on the notification I want to take the user to the rootVC from anywhere.
And using something like:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let destinationViewController = storyboard.instantiateViewController(withIdentifier: "accountVc") as? AccountViewController
navInTab.pushViewController(destinationViewController!, animated: false)
Does not work for eg: vc -> vc -> modalVc
since it will mess up the navigationstack
You can pop to the root of a navigation stack, or pop to the nth element in the stack, or replace the stack of view controllers with a different stack.
You can use popToRootViewController on the navigationController to go back to the very root of a navigation stack. Or suppose you want to go back to the first VC in the stack for example, you would do something like
let controllerStack = self.navigationController?.viewControllers
_ = self.navigationController?.popToViewController((controllerStack?[0])!, animated: true)
Or you can replace the navigation stack of view controllers with a new stack of controllers i.e.
NSMutableArray *controllerStack = [NSMutableArray arrayWithArray:navigationController.viewControllers];
// Replace the source controller with the destination controller, wherever the source may be
[controllerStack replaceObjectAtIndex:[controllerStack indexOfObject:sourceViewController] withObject:destinationController];
// Assign the updated stack with animation
[navigationController setViewControllers:controllerStack animated:YES];

Unable to go back through a navigation controller to a VC of another navigation controller using Swift

As you can see below, the notificationsVC is a part of the TabBarController which is embedded in a navigationContoller(lets call it first nC). Then theres a segue from notificationsVC to the second navigationController which will show the messagesVC.
There's a back button in messagesVC which when pressed should go back to notificationsVC
func backbutton() {
navigationController?.popViewControllerAnimated(true)
}
Now this is obviously not working because the navigationController will get the nearest NC and pop the VC in its stack but it won't let me go back to the notificationsVC.
Any other alternative?, although I've tried this with no success as well.
self.dismissViewControllerAnimated(true, completion: nil);
More detailed view
Also I'm using the JSQMessagesViewController library to show the messages in messagesVC which shouldn't matter but still worth mentioning. Thanks for your time!
You can access first NavigationViewController by asking it from TabBarViewController like in code below:
tabBarController?.navigationController?.popViewControllerAnimated(true)
Also asking navigation controller from you second navigation controller should work:
navigationController?.navigationController?.popViewControllerAnimated(true)
Your Navigation controller has only one VC i.e MessagesVc. So when you pop it,there is no other VC in the Navigation Controller's stack which can be presented. Your NotificationsVC is not in the Navigation controller's stack.
So I suggest you to do like this on back button click:
tabBarController?.selectedIndex = Index_Of_NotificationsVC
Try : -
let nVC = self.navigationController?.tabBarController?.navigationController?.storyboard?.instantiateViewControllerWithIdentifier("NotificationStoryboardVC_ID") as! NotificationVC
navigationController?.tabBarController?.navigationController?.pushViewController(nVC, animated: true)

Programmatically defining a new navigation controller order/stack?

I have a NavigationController that the following VC's are embedded in: VC1 -> VC2 -> VC3 -> VC4 -> VC5. My problem is that when I segue from VC5 (after editing is completed), I send you back to VC3, but I want to programmatically throw VC4 and VC5 off the stack, i.e. when the user is sent back to VC3, I want "back" in the navagitionBar to take you to VC2 (and not VC5 where you really came from).
This comes up a lot in IOS, where you want to edit the model, then send them back to the tableView/Collection view, but since editing is done, you don't want the editing viewControllers in the navigation stack anymore as its too confusing of UX.
In the screenshot below, the VC on the top right is VC5: which is segued back to the PinViewController (VC3) via self.performSegueWithIdentifier("backToPins", sender: self)
How can I do this?
don't use segue to come back (pop).
you should use popToViewController and pass specific viewcontroller as argument to pop that viewcontroller.
for example if you want to go on 3rd view controller out of five then you can do something like below. you can just change index from viewcontroller array to go different view controller.
let viewControllers: [UIViewController] = self.navigationController!.viewControllers as [UIViewController];
self.navigationController!.popToViewController(viewControllers[viewControllers.count - 3], animated: true);
If you are using segue that means you add (push) new viewcontroller to navigation stack. in your example your stack after reaching 5th view is like,
VC1 - VC2 - VC3 - VC4 - VC5 (top of stack)
now if you performsegue to go back to VC3 then stack should be like this,
VC1 - VC2 - VC3 - VC4 - VC5 - VC3(top of stack)
and if you pop to VC3 then your stack is like,
VC1 - VC2 - VC3 (top of stack).
so pop viewcintrollers to go back don't use segue
hope this will help :)
The best way to do this is via an unwind segue.
In VC3 you define an appropriate unwind function:
#IBAction func unwind(segue:UIStoryboardSegue) {
if let sourceViewController = segue.sourceViewController as? VC5 {
let myNewData=sourceViewController.someProperty
self.someFunctionThatUpdatesScene()
}
Then in the VC5 scene you can create an unwind segue in one of two ways.
If you want it to be triggered directly from an object, such as a UIButton, you drag from the action in the inspector to the exit icon at the top of the scene and select unwind from the pop up.
If you want to trigger the unwind programatically then you drag from the view controller object in the explorer on the left to the exit icon and select unwind from the popup. You will now see an unwind segue in the explorer and you can give it an identifier like you would with any segue. You can use this identifier with performSegueWithIdentifier
The advantage of this approach is that you don't need to make any assumptions about the depth of UINavigationController stack and you don't need to implement a delegate/protocol to pass data back.
Apple has a very good Tech Note on Using Unwind Segues

UINavigationController Storyboard Hopping

I would like to know what code is required to traverse the storyboard from a UIViewController located at index N of a UINavigationController which is embedded in a UITabBarController, to a similarly embedded UIViewController.
I would also like all UIViewControllers to be popped in the source UINavigationController
Direct segues (as shown in red) do not fit my use case.
Swift please.
You can pop to the root view controller then change the selected index of the index of the tab bar controller then push whatever view controllers you need on the other navigation controller. For example:
let tabBarController = self.tabBarController;
let indexZeroNavController:UINavigationController = self.tabBarController?.viewControllers![0] as! UINavigationController
self.navigationController?.popToRootViewControllerAnimated(true)
tabBarController?.selectedIndex = 0
let newViewController = self.storyboard?.instantiateViewControllerWithIdentifier("New View Controller")
let otherNewViewController = self.storyboard?.instantiateViewControllerWithIdentifier("Other New View Controller")
indexZeroNavController.pushViewController(newViewController!, animated: true)
indexZeroNavController.pushViewController(otherNewViewController!, animated: true)
Beyowulf's approach used to be valid, but things have changed. In the viewController you wish to pop to, define an "unwind segue". example Once it's defined, you can drag from a button to "exit" and select that unwind segue.
The way unwind segues work has been completely reworked in xcode 7, so you don't have to worry too much about the view controller stack.

Instead of push segue how to replace view controller (or remove from navigation stack)?

I have a small iPhone app, which uses a navigation controller to display 3 views (here fullscreen):
First it displays a list of social networks (Facebook, Google+, etc.):
Then it displays an OAuth dialog asking for credentials:
And (after that, in same UIWebView) for permissions:
Finally it displays the last view controller with user details (in the real app this will be the menu, where the multiplayer game can be started):
This all works well, but I have a problem, when the user wants to go back and select another social network:
The user touches the back button and instead of being displayed the first view, is displayed the second one, asking for OAuth credentials/permissions again.
What can I do here? Xcode 5.0.2 shows a very limited choice for segues - push, modal (which I can't use, because it obscures navigation bar needed for my game) and custom.
I am an iOS programming newbie, but earlier I have developed an Adobe AIR mobile app and there it was possible to 1) replace view instead of pushing and 2) remove an unneeded view from navigation stack.
How to do the same in a native app please?
To expand on the various segues above, this is my solution. It has the following advantages:
Can work anywhere in the view stack, not just the top view (not sure if this is realistically ever needed or even technically possible to trigger, but hey it's in there).
It doesn't cause a pop OR transition to the previous view controller before displaying the replacement, it just displays the new controller with a natural transition, with the back navigation being to the same back navigation of the source controller.
Segue Code:
- (void)perform {
// Grab Variables for readability
UIViewController *sourceViewController = (UIViewController*)[self sourceViewController];
UIViewController *destinationController = (UIViewController*)[self destinationViewController];
UINavigationController *navigationController = sourceViewController.navigationController;
// Get a changeable copy of the stack
NSMutableArray *controllerStack = [NSMutableArray arrayWithArray:navigationController.viewControllers];
// Replace the source controller with the destination controller, wherever the source may be
[controllerStack replaceObjectAtIndex:[controllerStack indexOfObject:sourceViewController] withObject:destinationController];
// Assign the updated stack with animation
[navigationController setViewControllers:controllerStack animated:YES];
}
You could use a custom segue: to do it you need to create a class subclassing UIStoryboardSegue (example MyCustomSegue), and then you can override the "perform" with something like this
-(void)perform {
UIViewController *sourceViewController = (UIViewController*)[self sourceViewController];
UIViewController *destinationController = (UIViewController*)[self destinationViewController];
UINavigationController *navigationController = sourceViewController.navigationController;
// Pop to root view controller (not animated) before pushing
[navigationController popToRootViewControllerAnimated:NO];
[navigationController pushViewController:destinationController animated:YES];
}
At this point go to Interface Builder, select "custom" segue, and put the name of your class (example MyCustomSegue)
In swift
override func perform() {
if let navigationController = self.source.navigationController {
navigationController.setViewControllers([self.destination], animated: true)
}
}
The custom segue didn't work for me, as I had a Splash view controller and I wanted to replace it. Since there was just one view controller in the list, the popToRootViewController still left the Splash on the stack. I used the following code to replace the single controller
-(void)perform {
UIViewController *sourceViewController = (UIViewController*)[self sourceViewController];
UIViewController *destinationController = (UIViewController*)[self destinationViewController];
UINavigationController *navigationController = sourceViewController.navigationController;
[navigationController setViewControllers:#[destinationController] animated:YES];
}
and now in Swift 4:
class ReplaceSegue: UIStoryboardSegue {
override func perform() {
source.navigationController?.setViewControllers([destination], animated: true)
}
}
and now in Swift 2.0
class ReplaceSegue: UIStoryboardSegue {
override func perform() {
sourceViewController.navigationController?.setViewControllers([destinationViewController], animated: true)
}
}
For this problem, I think the answer is just simple as
Get the array of view controllers from NavigationController
Removing the last ViewController (current view controller)
Insert a new one at last
Then set the array of ViewControllers back to the navigationController
as bellow:
if let navController = self.navigationController {
let newVC = DestinationViewController(nibName: "DestinationViewController", bundle: nil)
var stack = navController.viewControllers
stack.remove(at: stack.count - 1) // remove current VC
stack.insert(newVC, at: stack.count) // add the new one
navController.setViewControllers(stack, animated: true) // boom!
}
works perfectly with Swift 3.
Hope it helps for some new guys.
Cheers.
the swift 2 version of ima747 answer:
override func perform() {
let navigationController: UINavigationController = sourceViewController.navigationController!;
var controllerStack = navigationController.viewControllers;
let index = controllerStack.indexOf(sourceViewController);
controllerStack[index!] = destinationViewController
navigationController.setViewControllers(controllerStack, animated: true);
}
As he mentioned it has the following advantages:
Can work anywhere in the view stack, not just the top view (not sure if this is realistically ever needed or even technically possible to trigger, but hey it's in there).
It doesn't cause a pop OR transition to the previous view controller before displaying the replacement, it just displays the new controller with a natural transition, with the back navigation being to the same back navigation of the source controller.
Using unwind segue would be the most appropriate solution to this problem. I agree with Lauro.
Here is a brief explanation to setup an unwind segue from detailsViewController[or viewController3] to myAuthViewController[or viewController1]
This is essentially how you would go about performing an unwind segue through the code.
Implement an IBAction method in the viewController you want to unwind to(in this case viewController1). The method name can be anything so long that it takes one argument of the type UIStoryboardSegue.
#IBAction func unwindToMyAuth(segue: UIStoryboardSegue) {
println("segue with ID: %#", segue.Identifier)
}
Link this method in the viewController(3) you want to unwind from. To link, right click(double finger tap) on the exit icon at the top of the viewController, at this point 'unwindToMyAuth' method will show in the pop up box. Control click from this method to the first icon, the viewController icon(also present at the top of the viewController, in the same row as the exit icon). Select the 'manual' option that pops up.
In the Document outline, for the same view(viewController3), select the unwind segue you just created. Go to the attributed inspector and assign a unique identifier for this unwind segue. We now have a generic unwind segue ready to be used.
Now, the unwind segue can be performed just like any other segue from the code.
performSegueWithIdentifier("unwind.to.myauth", sender: nil)
This approach, will take you from viewController3 to viewController1 without the need to remove viewController2 from the navigation hierarchy.
Unlike other segues, unwind segues do not instantiate a view controller, they only go to an existing view controller in the navigation hierarchy.
As mentioned in previous answers to pop not animated and then to push animated won't look very good because user will see the actual process.
I recommend you first push animated and then remove the previous vc. Like so:
extension UINavigationController {
func replaceCurrentViewController(with viewController: UIViewController, animated: Bool) {
pushViewController(viewController, animated: animated)
let indexToRemove = viewControllers.count - 2
if indexToRemove >= 0 {
viewControllers.remove(at: indexToRemove)
}
}
}
Here a quick Swift 4/5 solution by creating a custom seque that replaces the (top stack) viewcontroller with the new one (without animation) :
class SegueNavigationReplaceTop: UIStoryboardSegue {
override func perform () {
guard let navigationController = source.navigationController else { return }
navigationController.popViewController(animated: false)
navigationController.pushViewController(destination, animated: false)
}
}
Use below code last view controller
You can use other button or put it your own instead of cancel button i have used
- (void)viewDidLoad
{
[super viewDidLoad];
[self.navigationController setNavigationBarHidden:YES];
UIBarButtonItem *cancelButton = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:#selector(dismiss:)];
self.navigationItemSetting.leftBarButtonItem = cancelButton;
}
- (IBAction)dismissSettings:(id)sender
{
// your logout code for social media selected
[self.navigationController popToRootViewControllerAnimated:YES];
}
What you should really do is modally present a UINavigationController containing the social network UIViewControllers overtop of your Menu UIViewController (which can be embedded in a UINavigationController if you want). Then, once a user has authenticated, you dismiss the social network UINavigationController, showing your Menu UIViewController again.
In swift3 create one segue
-add identifier
-add and set in segue(storyboard) custom storyboard class from cocoatouch file
-In custom class override perform()
override func perform() {
let sourceViewController = self.source
let destinationController = self.destination
let navigationController = sourceViewController.navigationController
// Pop to root view controller (not animated) before pushing
if self.identifier == "your identifier"{
navigationController?.popViewController(animated: false)
navigationController?.pushViewController(destinationController, animated: true)
}
}
-You also have to override one method in your source viewcontroller
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
return false
}
Well, what you can also do is to use the unwind view controller stuff.
Actually I think that this is exactly what you need.
Check this entry: What are Unwind segues for and how do you use them?
This worked for me in Swift 3:
class ReplaceSegue: UIStoryboardSegue {
override func perform() {
if let navVC = source.navigationController {
navVC.pushViewController(destination, animated: true)
} else {
super.perform()
}
}
}
How about this :) I now it's old question, but this will work as a charm:
UIViewController *destinationController = [[UIViewController alloc] init];
UINavigationController *newNavigation = [[UINavigationController alloc] init];
[newNavigation setViewControllers:#[destinationController]];
[[[UIApplication sharedApplication] delegate] window].rootViewController = newNavigation;

Resources