How to do something before unwind segue action? - ios

I've just figured out what is an unwind segue and how to use it with the great answers from this question. Thanks a lot.
However, here's a question like this:
Let's say there is a button in scene B which unwind segues to scene A, and before it segues I want something to be done, such as saving the data to database. I created an action for this button in B.swift, but it seems it goes directly to scene A without taking the action appointed.
Anyone knows why or how to do this?
Thank you.

You can do it the way you describe, or by using a prepareForSegue override function in the viewController you are unwinding from:
#IBAction func actionForUnwindButton(sender: AnyObject) {
println("actionForUnwindButton");
}
or...
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
println("prepareForSegue");
}
The first example is as you describe. The button is wired up to the unwind segue and to the button action in Interface Builder. The button action will be triggered before the segue action. Perhaps you didn't connect the action to the button in interface builder?
The second example gives you have access to the segue's sourceViewController and destinationViewController in case that is also useful (you also get these in the unwind segue's function in the destination view controller).
If you want to delay the unwind segue until the button's local action is complete, you can invoke the segue directly from the button action (instead of hooking it up in the storyboard) using self.performSegueWithIdentifier (or follow wrUS61's suggestion)
EDIT
you seem to have some doubts whether you can work this by wiring up your button both to an unwind segue and to a button action. I have set up a little test project like this:
class BlueViewController: UIViewController {
#IBAction func actionForUnwindButton(sender: AnyObject) {
println("actionForUnwindButton");
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
println("prepareForSegue");
}
}
class RedViewController: UIViewController {
#IBAction func unwindToRed(sender: UIStoryboardSegue) {
println("unwindToRed");
}
}
BlueViewController has a button that is connected in the storyboard to BOTH the unwindToRed unwind segue AND the actionForUnwindButton button. It also overrides prepareForSegue so we can log the order of play.
Output:
actionForUnwindButton
prepareForSegue
unwindToRed
Storyboard:
EDIT 2
your demo project shows this not working. The difference is that you are using a barButtonItem to trigger the action, whereas I am using a regular button. A barButtonItem fails, whereas a regular button succeeds. I suspect that this is due to differences in the order of message passing (what follows is conjecture, but fits with the observed behaviour):
(A) UIButton in View Controller
ViewController's button receives touchupInside
- (1) sends action to it's method
- (2) sends segue unwind action to storyboard segue
all messages received, and methods executed in this order:
actionForUnwindButton
prepareForSegue
unwindToRed
(B) UIBarButtonItem in Navigation Controller Toolbar
Tool bar buttonItem receives touchupInside
- (1) sends segue unwind action to storyboard segue
- (2) (possibly, then) sends action to viewController's method
Order of execution is
prepareForSegue
unwindToRed
actionForUnwindButton
prepareForSegue and unwind messages received. However actionForUnwindButton message is sent to nil as viewController is destroyed during the segue. So it doesn't get executed, and the log prints
prepareForSegue
unwindToRed
In the case of (B), the viewController is destroyed before the method reaches it, so does not get triggered
So it seems your options are...
(a) use a UIButton with action and unwind segue
(b) trigger your actions using prepareForSegue, which will be triggered while the viewController is still alive, and before the segue takes place.
(c) don't use an unwind segue, just use a button action. In the action method you can 'unwind' by calling popToViewController on your navigation controller.
By the way, if you implement a toolBar on the viewController (not using the navigation controller's toolbar) the result is the same: segue gets triggered first, so button action fails.

If you are able to perform unWind Segue Successfully. Then the method in destination View Controller is called just before the segue take place, you can do what ever you want in source viewcontroller by using the segue object.
- (IBAction)unwindToThisViewController:(UIStoryboardSegue *)unwindSegue
{
CustomViewController *vc = (CustomViewController*) unwindSegue.sourceViewController;
[vc performAnyMethod];
[vc saveData];
NSString *temp = vc.anyProperty;
}
if you want your logic in source Controller then implement prepareForSegue in Scene B and set the unWind segue Identifier from Storyboard > Left View hierarchy Panel > under Exit in Scene B.
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([[segue identifier] isEqualToString:#"backToSource"])
{
NSLog(#"Going Back");
}
}

At first, you should call the send data function in prepareForSegue method.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if([[segue identifier] isEqualToString:#"UnwindIdentifier"]) {
// send data
}
}
If you don't want to let unwind segue happen before getting response from the server, you should override
- (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender
method and return NO;. Then you can perform segue manually when you get the server response by calling:
[self performSegueWithIdentifier:#"UnwindIdentifier" sender:sender];

Related

Recreate dismiss animation in Swift [duplicate]

Using storyboard this is very easy. You just drag the action to "Exit". But how should I call it from my code?
Create a manual segue (ctrl-drag from File’s Owner to Exit),
Choose it in the Left Controller Menu below green EXIT button.
Insert Name of Segue to unwind.
Then,- (void)performSegueWithIdentifier:(NSString *)identifier sender:(id)sender. with your segue identify.
Here's a complete answer with Objective C and Swift:
1) Create an IBAction unwind segue in your destination view controller (where you want to segue to). Anywhere in the implementation file.
// Objective C
- (IBAction)unwindToContainerVC:(UIStoryboardSegue *)segue {
}
// Swift
#IBAction func unwindToContainerVC(segue: UIStoryboardSegue) {
}
2) On the source view controller (the controller you're segueing from), ⌃ + drag from "Name of activity" to exit. You should see the unwind segue created in step 1 in the popup. (If you don't see it, review step one). Pick unwindToContainerVC: from the popup, or whatever you named your method to connect your source controller to the unwind IBAction.
3) Select the segue in the source view controller's document outline of the storyboard (it will be listed near the bottom), and give it an identifier.
4) Call the unwind segue using this method from source view controller, substituting your unwind segue name.
// Objective C
[self performSegueWithIdentifier:#"unwindToContainerVC" sender:self];
// Swift
self.performSegueWithIdentifier("unwindToContainerVC", sender: self)
NB. Use the sourceViewController property of the segue parameter on the unwind method to access any exposed properties on the source controller. Also, notice that the framework handles dismissing the source controller. If you'd like to confirm this add a dealloc method to the source controller with a log message that should fire once it has been killed. If dealloc doesn't fire you may have a retain cycle.
bradleygriffith's answer was great. I took step 10 and made a screenshot for simplification. This is a screenshot in Xcode 6.
Control-drag from the orange icon to the red Exit icon to create an unwind without any actions/buttons in the view.
Then select the unwind segue in the sidebar:
Set a Segue Identifier string:
Access that identifier from code:
[self performSegueWithIdentifier:#"unwindIdentifier" sender:self];
I used [self dismissViewControllerAnimated: YES completion: nil]; which will return you to the calling ViewController.
Quoting text from Apple's Technical Note on Unwind Segue:
To add an unwind segue that will only be triggered programmatically, control+drag from the scene's view controller icon to its exit icon, then select an unwind action for the new segue from the popup menu.
Link to Technical Note
Vishal Chaudhry's answer above worked for me. I would also add that in order to manually trigger the seque using:
[self performSegueWithIdentifier:#"mySegueName" sender:self];
from within the ViewController you must also select the unwind segue under the ViewController's Scene in the storyboard and in the properties view on the RHS ensure that the Indentifier field contains the namer you're referring to in the code ("mySegueName" in the example above).
If you omit this step, the line above will throw an exception that the seque name is not known.
SWIFT 4:
1. Create an #IBAction with segue inside controller you want to unwind to:
#IBAction func unwindToVC(segue: UIStoryboardSegue) {
}
2. In the storyboard, from the controller you want to segue (unwind) from ctrl+drag from the controller sign to exit sign and choose method you created earlier:
3. Now you can notice that in document outline you have new line with title "Unwind segue....". Now you should click on this line and open attribute inspector to set identifier (in my case unwindSegueIdentifier).
4. You're almost done! Now you need to open view controller you wish to unwind from and create some method that will perform segue. For example you can add button, connect it with code with #IBAction, after that inside this IBAction add perfromSegue(withIdentifier:sender:) method:
#IBAction func unwindToSomeVCWithSegue(_ sender: UIButton) {
performSegue(withIdentifier: "unwindSegueIdentifier", sender: nil)
}
So that is all you have to do!
Swift 4.2, Xcode 10+
For those wondering how to do this with VCs not set up via the storyboard (those coming to this question from searching "programmatically" + "unwind segue").
Given that you cannot set up an unwind segue programatically, the simplest solely programmatic solution is to call:
navigationController?.popToRootViewController(animated: true)
which will pop all view controllers on the stack back to your root view controller.
To pop just the topmost view controller from the navigation stack, use:
navigationController?.popViewController(animated: true)
Backwards compatible solution that will work for versions prior to ios6, for those interested:
- (void)unwindToViewControllerOfClass:(Class)vcClass animated:(BOOL)animated {
for (int i=self.navigationController.viewControllers.count - 1; i >= 0; i--) {
UIViewController *vc = [self.navigationController.viewControllers objectAtIndex:i];
if ([vc isKindOfClass:vcClass]) {
[self.navigationController popToViewController:vc animated:animated];
return;
}
}
}
FYI: In order for #Vadim's answer to work with a manual unwind seque action called from within a View Controller you must place the command:
[self performSegueWithIdentifier:(NSString*) identifier sender:(id) sender];
inside of the overriden class method viewDidAppear like so:
-(void) viewDidAppear:(BOOL) animated
{
[super viewDidAppear: animated];
[self performSegueWithIdentifier:#"SomeSegueIdentifier" sender:self];
}
If you put it in other ViewController methods like viewDidLoad or viewWillAppear it will be ignored.

Hiding back button in viewDidLoad without animation

I'm using navigationItem.setHidesBackButton(true, animated:false) to hide back button in my iOS10 app in viewDidLoad. When I do this, on navigation bar, back button label is briefly shown (it is fading out) in presenting animation instead not showing it at all, after screen change, button is gone.
How I can prevent it from happening?
Suppose you are going from Controller A to Controller B.
Currently you are applying self.navigationItem.hidesBackButton = true in the viewDidLoad of Controller B.
Add this very same code when you are pushing from A to B in Controller A's viewWillDisappear OR prepareForSegue (if you are using segue)
-(void)viewWillDisappear:(BOOL)animated{
self.navigationItem.hidesBackButton = true;
}
A safer option is in prepareForSegue as viewWillDisappear will get called whenever this Controller A is going OFF-SCREEN. But in prepareForSegue, you can check that the following code will work only when its going from Controller A to Controller B, by the following
Suppose the segue connecting from Controller A to Controller B is named "SEGUE_NAME"
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if([[segue identifier]isEqualToString:#"SEGUE_NAME"]){
/*
this means it is going from Controller A to Controller B
via segue "SEGUE_NAME"
*/
self.navigationItem.hidesBackButton = YES;
}
}
I think this would be the swift version : Forgive me if the syntax isn't appropriate
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if(segue.identifier == "SEGUE_NAME"){
self.navigationItem.hidesBackButton = true;
}
}

Unwind segue not transitioning properly

I use a push segue to transition from a uisearchcontroller located within my root view controller, to a second view controller. When I try to use an unwind segue method to transition back to the root view controller from my second view controller, my app does not transition unless the button connected to the unwind method is pressed twice. The unwind method is called both times, however the transition only occurs upon the second call. I do not know why this occurs. Any help is appreciated. Thanks!
Unwind segue method
#IBAction func saveWordAndDefinition(segue:UIStoryboardSegue) {
self.searchController.active = false
if let definitionViewController = segue.sourceViewController as? DefinitionViewController {
saveWordToCoreData(definitionViewController.word)
}
tableView.reloadData()
}
How I linked my segue
Unwind segue
While what you're doing is permissible, it seems to be against best practice. The functionality of presenting a view controller, UITableViewController in this case, entering information, then later dismissing it with a button in the upper-right hand corner is generally associated with a modal view. In a push segue you'll get the back button in the upper-left corner for free, which will enable to you to pop the view controller off the stack without writing extra code.
Here's another Stack Overflow question that describe: What is the difference between Modal and Push segue in Storyboards?
To answer your question specifically, here are a couple links that should help:
[self.navigationController popViewControllerAnimated:YES]; is probably what you're looking for.
Dismiss pushed view from within Navigation Controller
How can I dismiss a pushViewController in iPhone/iPad?
So here's how I finally got this to work:
In my FirstViewController (the vc i'm unwinding to):
Here is my unwind segue method.
#IBAction func saveWordAndDefinition(segue:UIStoryboardSegue) {
self.navigationController?.popViewControllerAnimated(false)
}
Then I gave my unwind segue the identifier "unwind" in Storyboard.
In my SecondViewController (the vc i'm unwinding from):
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "unwind" {
if let destination = segue.destinationViewController as? VocabListViewController {
destination.saveWordToCoreData(word)
destination.tableView.reloadData()
}
}
}
I took care of passing data in the prepareForSegue method of my SecondViewController. Thanks to #Lory Huz for the suggestion. I finally figured out what you meant by it.
Works without any errors!

prepareForSegue is not getting called when I click on a button without using performSegueWithIdentifier

Based on the Stanford iOS course I am playing with modal view controllers. In the demo they have a button that would launch a modal view and when it is clicked the function prepareForSegue is called. I mimicked the code and implementation into my project with the only difference is that my demo is on an iPhone storyboard and theirs is on the iPad.
I noticed that while my modal view controller is coming up, it does not call prepareForSegue prior to that. I searched the Stanford project to see where they may register any segue behavior before prepareForSegue is called but there is no evidence. Can anyone shed some light on this. I searched stack overflow and all I found were that users were missing the call implementation of performSegueWithIdentifier. However, in the Stanford demo they never do that.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier hasPrefix:#"Create Label"]) {
AskerViewController *asker = (AskerViewController *)segue.destinationViewController;
asker.question = #"What do you want your label to say?";
asker.answer = #"Label Text";
asker.delegate = self;
}
}
Here is an example of there storyboard:
Here is an example of my storyboard:
In the debugger when I stop in the Stanford Demo code the call stack shows that the storyboard is performing a segue action, what do I need to configure in my storyboard to achieve the same result?
Well, as it turns out, my view controller where button calls the modal view did not have the right class where prepareForSegue is implemented. It was the default UIViewController instead of my custom class.
The way I figured it out was by putting a break point in viewDidLoad and even that was not breaking and thus I suspected that in the storyboard I did not have the right class associated with the view where the button is implemented.
For others with this problem, if you are now using Swift 3 having the following function will not throw errors as it is the correct syntax but it will not work because it is the Swift 2 function:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// code
}
You must update to Swift 3 "prepare" function for it to work for segues:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// code
}
When hooking up an automatic segue for a table view, there is, in addition to Amro's answer (not assigning your corresponding subclass), another two cases where prepareForSegue might not be called. Ensure you've:
hooked up the segue from the table view prototype cell, not the table view controller.
used a segue from under the "Selection Segue" group in the segue connection pop-up menu, not one under "Accessory Action".
[Click image to enlarge]
Whether its an Modal or Push Segue below code will always be called
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"Create Label"]) {
SignUpViewController *asker = segue.destinationViewController;
}
}
I had a similar problem with UICollectionViewCell as the source of segue.
It seems that for the storyboard segue to work (without performSegueWithIdentifier) it's required that you have a custom subclass of UICollectionViewCell, and use it as a Class for the CollectionViewCell on the story board.
I had a similar issue.
The thought process was:
make sure you have the correct method signature. It has changed in Swift 3.
Then make sure the way you have hooked up the button (or whatever that triggers the segue) matches with the way you have hooked the segue in storyboard. Sometimes you call a button, but haven't properly hooked up the segue from that button to the destination viewcontroller.
Be sure the identifier of the segue is correct. Though this isn't the reason the prepareForSegue doesn't get called, this only the reason that a specific segue isn't called.
In my case, it ocured because my controller was extending another controller (Eureka Form View Controller = FormViewController) witch has implemented the performSegue function like this:
open override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// code
}
My function was implemented like this:
func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// code
}
To solve this, i just added override before:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// code
}
Voila!
In my case, I did not set the module under the name of the class in the storyboard of the view controller that contains the segue. It was set to none and once I clicked in the module field it set to the correct project / module and resolved the issue.
In my case, I have a base class for several view controllers in my project. That base class has an implementation of prepareForSegue() which wasn't getting called in every case. The problem was that in one of my view controllers that inherits from the base class and overrides its prepareForSegue() implementation, I forgot to call super.prepareForSegue().
Firstly you have to select on your button + ctrl drag item to you view controller choose selection segue .Later, you have to name segue identifier properly.
Don't connect to one view controller to other view controller.
import UIKit.framework for this
Then this method will get called.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"identifierName"])
{
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
ExampleViewController *destViewController = segue.destinationViewController;
}
}

How to perform Unwind segue programmatically?

Using storyboard this is very easy. You just drag the action to "Exit". But how should I call it from my code?
Create a manual segue (ctrl-drag from File’s Owner to Exit),
Choose it in the Left Controller Menu below green EXIT button.
Insert Name of Segue to unwind.
Then,- (void)performSegueWithIdentifier:(NSString *)identifier sender:(id)sender. with your segue identify.
Here's a complete answer with Objective C and Swift:
1) Create an IBAction unwind segue in your destination view controller (where you want to segue to). Anywhere in the implementation file.
// Objective C
- (IBAction)unwindToContainerVC:(UIStoryboardSegue *)segue {
}
// Swift
#IBAction func unwindToContainerVC(segue: UIStoryboardSegue) {
}
2) On the source view controller (the controller you're segueing from), ⌃ + drag from "Name of activity" to exit. You should see the unwind segue created in step 1 in the popup. (If you don't see it, review step one). Pick unwindToContainerVC: from the popup, or whatever you named your method to connect your source controller to the unwind IBAction.
3) Select the segue in the source view controller's document outline of the storyboard (it will be listed near the bottom), and give it an identifier.
4) Call the unwind segue using this method from source view controller, substituting your unwind segue name.
// Objective C
[self performSegueWithIdentifier:#"unwindToContainerVC" sender:self];
// Swift
self.performSegueWithIdentifier("unwindToContainerVC", sender: self)
NB. Use the sourceViewController property of the segue parameter on the unwind method to access any exposed properties on the source controller. Also, notice that the framework handles dismissing the source controller. If you'd like to confirm this add a dealloc method to the source controller with a log message that should fire once it has been killed. If dealloc doesn't fire you may have a retain cycle.
bradleygriffith's answer was great. I took step 10 and made a screenshot for simplification. This is a screenshot in Xcode 6.
Control-drag from the orange icon to the red Exit icon to create an unwind without any actions/buttons in the view.
Then select the unwind segue in the sidebar:
Set a Segue Identifier string:
Access that identifier from code:
[self performSegueWithIdentifier:#"unwindIdentifier" sender:self];
I used [self dismissViewControllerAnimated: YES completion: nil]; which will return you to the calling ViewController.
Quoting text from Apple's Technical Note on Unwind Segue:
To add an unwind segue that will only be triggered programmatically, control+drag from the scene's view controller icon to its exit icon, then select an unwind action for the new segue from the popup menu.
Link to Technical Note
Vishal Chaudhry's answer above worked for me. I would also add that in order to manually trigger the seque using:
[self performSegueWithIdentifier:#"mySegueName" sender:self];
from within the ViewController you must also select the unwind segue under the ViewController's Scene in the storyboard and in the properties view on the RHS ensure that the Indentifier field contains the namer you're referring to in the code ("mySegueName" in the example above).
If you omit this step, the line above will throw an exception that the seque name is not known.
SWIFT 4:
1. Create an #IBAction with segue inside controller you want to unwind to:
#IBAction func unwindToVC(segue: UIStoryboardSegue) {
}
2. In the storyboard, from the controller you want to segue (unwind) from ctrl+drag from the controller sign to exit sign and choose method you created earlier:
3. Now you can notice that in document outline you have new line with title "Unwind segue....". Now you should click on this line and open attribute inspector to set identifier (in my case unwindSegueIdentifier).
4. You're almost done! Now you need to open view controller you wish to unwind from and create some method that will perform segue. For example you can add button, connect it with code with #IBAction, after that inside this IBAction add perfromSegue(withIdentifier:sender:) method:
#IBAction func unwindToSomeVCWithSegue(_ sender: UIButton) {
performSegue(withIdentifier: "unwindSegueIdentifier", sender: nil)
}
So that is all you have to do!
Swift 4.2, Xcode 10+
For those wondering how to do this with VCs not set up via the storyboard (those coming to this question from searching "programmatically" + "unwind segue").
Given that you cannot set up an unwind segue programatically, the simplest solely programmatic solution is to call:
navigationController?.popToRootViewController(animated: true)
which will pop all view controllers on the stack back to your root view controller.
To pop just the topmost view controller from the navigation stack, use:
navigationController?.popViewController(animated: true)
Backwards compatible solution that will work for versions prior to ios6, for those interested:
- (void)unwindToViewControllerOfClass:(Class)vcClass animated:(BOOL)animated {
for (int i=self.navigationController.viewControllers.count - 1; i >= 0; i--) {
UIViewController *vc = [self.navigationController.viewControllers objectAtIndex:i];
if ([vc isKindOfClass:vcClass]) {
[self.navigationController popToViewController:vc animated:animated];
return;
}
}
}
FYI: In order for #Vadim's answer to work with a manual unwind seque action called from within a View Controller you must place the command:
[self performSegueWithIdentifier:(NSString*) identifier sender:(id) sender];
inside of the overriden class method viewDidAppear like so:
-(void) viewDidAppear:(BOOL) animated
{
[super viewDidAppear: animated];
[self performSegueWithIdentifier:#"SomeSegueIdentifier" sender:self];
}
If you put it in other ViewController methods like viewDidLoad or viewWillAppear it will be ignored.

Resources