I have a view controller (in a navigation controller) A that segues to View Controller B, and View Controller C also segues to B (A -> B <- C). View Controller B is used to select a value that is passed back to either A or C, but if I click 'Cancel' or 'Done' it uses the unwind segue to go back to only one controller, whichever was connected last.
You can have an unwind segue return to whichever viewController initiated the segue. All you have to do is to implement the same method you are returning to in all of the viewControllers which segue to viewController B.
So in viewController A and viewController C, implement the following method:
#IBAction func backFromB(segue: UIStoryboardSegue) {
print("Back from B")
}
Then, when you Control-drag from your Cancel button in viewController B to the Exit icon at the top of the viewController, select backFromB from the pop-up.
Then when you run the app and hit Cancel in viewController B, you will return to either viewController A or viewController C (whichever one segued to B). This even works if one segue is a Show (Push) and the other is Modal.
Related
I'm studying the tutorial from Apple Developer: Start Developing iOS Apps (Swift), and I'm confused with push and modal segue.
There are two scenarios, Save and Cancel button in navigation bar, backing to scene 1 from scene 2.
If the Cancel button is pressed, it will call different method for dismissing scene 2:
#IBAction func cancel(_ sender: UIBarButtonItem) {
// Depending on style of presentation (modal or push presentation), this view controller needs to be dismissed in two different ways.
let isPresentingInAddMealMode = presentingViewController is UINavigationController
if isPresentingInAddMealMode {
dismiss(animated: true, completion: nil)
}
else if let owningNavigationController = navigationController{
owningNavigationController.popViewController(animated: true)
}
else {
fatalError("The MealViewController is not inside a navigation controller.")
}
}
In this method, if the scene is presented by modal segue, dismiss(animated:completion:) is called, and
if the scene is presented by push segue, popViewController(animated:) is called for poping up the ViewController from the navigation stack.
But for the Save button, the tutorial overrides a method in scene 2, prepare(for:sender:), and a action method in scene 1, unwindToMealList(sender:).
And it drag the Save button to Exit (the button in the scene dock) and choose unWindToMealList(sender:) method.
So the flow will be: prepare(for:sender:) -> scene 2 dismissed and scene 1 presented -> unWindToMealList(sender:)
I'm wondering that the code snippets didn't dismiss explictly scene 2 and remove the ViewController in navigation stack when Save button is pressed.
I know that modal segue won't push ViewController to navigation stack, but push segue will push it.
Why the code snippets doesn't pop up it from navigation stack?
Many thanks.
It seems like the tutorial you are reading is making use of unwind segues.
Unwind segues, just like normal segues, have a source and a destination and you can prepare for it in prepareForSegue, but instead of presenting the destination VC, it will dismiss the source VC so that the destination VC is shown.
Unwind segues behave differently in different situations. When you present VC B from VC A using a push segue, and an unwind segue from B to A, the unwind segue will pop VC B from the navigation stack. When you present VC B from VC A modally, the unwind segue will dismiss the modally presented VC.
As you can see, unwind segues are quite smart. It will decide for itself what to do in order to show the destination VC. It can even pop two or more VCs in the navigation stack!
I’m having a scenario,
I had 3 controllers, lets assume A,B and C. When app launches I’m transitioning from A to C using segue. Later, in C by using a button I’m moving to B using segue again. So, now I’m in B when I click “Back” it is transitioning to C, but it has to move to A.
How is this possible, any hint/idea?
Put this code in B controller's back button.
for viewcontroller in self.navigationController!.viewControllers as Array {
if viewcontroller.isKindOfClass(HomeVC) { // change HomeVC to your viewcontroller in which you want to back.
self.navigationController?.popToViewController(viewcontroller as! UIViewController, animated: true)
break
}
}
OR
If Class A is your RootView controller
self.navigationController?.popToRootViewControllerAnimated(true)
If your Class A viewController is root, you can set this code on back button:
self.navigationController?.popToRootViewControllerAnimated(true)
I have 3 MVCs and name them A, B, C here.
A is the main MVC with a button "Menu" connected to B with popover segue.
A also connected to C with a manual show segue.
B is a popover MVC with a button "Detail" connected to A with unwind segue.
C is the detail MVC with detail info.
Inside the unwind function of A. I call performSegueWithIdentifier to show C.
Expected behavior is
Click "Detail" button in B
B disappear and A show up
C show up
But running the app I got.
Click "Detail" button in B
B disappear and A show up
C show up
C disappear and A show up
C show up and disappear suddenly which is not what I want.
Some additional info
Popover B is needed for more buttons.
A is embeded in a UINavigationController. Connecting A -> C rather than B -> C, for a Back button on top of C.
Seems unwind function is not the correct place to call performSegueWithIdentifier.
UIKit will pop view controller after calling the unwind function.
Thus push new view controller inside unwind function will pop quickly.
The solution is to delay performSegueWithIdentifier.
Solution 1: NOT WORKING FINE
Adding an bool instance and inside viewDidAppear use this instance to determine if we perform segue.
Won't work if B controller is a popover. After B popover disappear, viewDidAppear is not called for A controller.
Solution 2: WORKING
Push performSegueWithIdentifier into main queue.
#IBAction func unwind(segue : UIStoryboardSegue) {
dispatch_async(dispatch_get_main_queue()) {
self.performSegueWithIdentifier("SomeSegue", sender: self)
}
}
you can easily switch between the viewControllers by assigning storyboard id to them using below code ..
// you need to create UIStoryboard object by giving name of your storyboard
UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
// here you need to create storyboard ID of perticular view where you need to navigate your app
UIViewController *vc = [mainStoryboard instantiateViewControllerWithIdentifier:#"viewContIdentifire"];
// if use presentViewController this will not enables you to go back to previous view
[self presentViewController:vc animated:NO completion:nil];
**// OR**
// using pushViewController lets you to go back to the previous view
[self.navigationController pushViewController:vc animated:YES];
Or still you want to work with segue so must gain control over them by implementing its delegate method below so they perform accordingly.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"segueName"]) {
// perform segue
[self performSegueWithIdentifier:#"SegueIdentifier" sender:self];
}
}
happy coding .. :)
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
I have 3 view controllers: A, B and C. A is initial and it has 2 buttons: one is redirecting to B, and second one to C. When B is pushed, and button within B pressed user is redirected to C. Now on navigation bar within C, when I press back button I want to be redirected to A always, and never to B. Is that possible?
This is base functionality I want to achieve:
a)
A->B->C C back to A
b)
A->C C back to A
for (UIViewController *controller in self.navigationController.viewControllers)
{
if ([controller isKindOfClass:[A class]])
{
[self.navigationController popToViewController:controller
animated:YES];
break;
}
}
you can achieve this with presentview controller as modal view.first from a if first button is pressed present b view.if secnd button is pressed present c view and in c view controller put back button and add delegates and implement the delegate in a view controller.and you will have to dismiss one view after the other in a view controller.
The only way is replacing the back button by a custom button.
After reading what you said I think you meant implementing custom actions for each button.The way I would do it(In Swift) is this:
#IBAction func pressButton(sender : UIButton){
var SecondView :ViewController2
SecondView = self.storyboard?.instantiateViewControllerWithIdentifier("SecondView") as ViewController2
self.presentViewController(SecondView, animated: true, completion:nil)
self.viewWillDisappear(true)
}
This method I have used a lot and I know there is another way using segues but you still use #IBActions.You just create a new segue(identifier) and in the prepareforSegue method you say what happens when that segue is triggered