Delegate / Unwind Segue at NavigationController Back Button - ios

i have an app with three views:
FirstView
SecondView
ThirdView
When i am on the ThirdView and i click on the back button from navigation controller, i want to pass data from ThridView to SecondView.
But i don't know how to do this.
I don't want to add an extra button on my view.
The same when i'm on the SecondView and i want to go back to the FirstView.
I can't use the method "ViewWillDisappear" because if a set a "PerformSegueWithIdentifier" on this method to pass data from SecondView to FirstView, i can't switch to ThirdView because the "ViewWillDisappear" method will be executed.
Can you please help me?
PS: I use the language swift 2

In your viewWillDisappear or viewDidDisappear method you can check if back button is pressed by checking whether or not your view controller is moving from parent view controller.
override func viewWillDisappear(animated: Bool) {
if(self.isMovingFromParentViewController())
{
//Send data to previous view controller
print("Going back: Back button pressed");
}else{
print("Controller is hidden due to some other reason e.g Pushing another view controller");
}
}

I make subclass of UINavigationController because I cannot find Unwind Segue signature in UIViewController since Xcode 11.
#implementation CLUnwindNavigationController
- (BOOL)canPerformUnwindSegueAction:(SEL)action fromViewController:(UIViewController *)fromViewController withSender:(id)sender {
return NO;
}
- (IBAction)unwindForSegue:(UIStoryboardSegue *)unwindSegue towardsViewController:(UIViewController *)subsequentVC {
}
#end
You can find UnwindSegue signature.
You can connect to your desired action.

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.

Objective-C: UINavigationBar not update when go back to root controller

this is my question. I have three View Controllers (VC1, VC2 and VC3). Every View Controller inherited from UINavigationControllerDelegate and I delegate my navigation controller into the viewWillAppear method in this way:
self.navigationController.delegate = self;
And the only UINavigationControllerDelegate method that I use is for transition:
- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
animationControllerForOperation:(UINavigationControllerOperation)operation
fromViewController:(UIViewController *)fromVC
toViewController:(UIViewController *)toVC {
if (operation == UINavigationControllerOperationPush || operation == UINavigationControllerOperationPop) {
myTransitionController *transitionController = [[myTransitionController alloc] init];
return transitionController;
}else{
return nil;
}
}
My problem is this: when I walk from VC1 to VC2 and into this controller trigger a NSNotification, I execute popViewControllerAnimated to go back the previous View Controller (VC1). This is the code:
- (void) backToRoot: (id) sender{
[self.navigationController popViewControllerAnimated:YES];
}
and the UINavigationBar show correctly its appearance (UINavigationBar without back button). Then, when I walk from VC1 to VC2 until VC3, and programatically go back until VC1 with popToRootViewController, the transition of the views (VC3-VC2-VC1) it works perfectly, but the UINavigationBar appearance not update and it shows with back button in the VC1 (which is the root View Controller). The method in the VC3 that I implemented is this:
- (void) goToRoot{
[self.navigationController popToRootViewControllerAnimated:YES];
}
I have tried with a double call of popViewControllerAnimated, I added in viewWillAppear and viewDidAppear method into VC2 and It not works neither. Anyone have idea what happens?
According to the documentation the func Pops all the view controllers on the stack except the root view controller and updates the display but I can't figure out the problem. You can try with unwind segue, is really simple.
What are Unwind segues for and how do you use them?

Perform code when unwind segue with back button

I want to perform some code when the segue goes back one step. For example, when the back button gets selected, I want to perform some code.
I can't create a new unwind action from the back button because there is no back button in the storyboard. Is there a way to insert code, as soon as the back button is selected?
If you want to stick with the default back button, one way to do this is to subclass the navigation controller, and override popViewControllerAnimated: which is called when you tap the back button.
-(UIViewController *)popViewControllerAnimated:(BOOL)animated {
UIViewController *vcToBePopped =[super popViewControllerAnimated:animated];
id vc = self.viewControllers.lastObject; // this will be the controller you're going back to
if ([vc isKindOfClass:IntendedViewController class]) { // replace IntendedViewController with the actual class name you care about
[(IntendedViewController *)vc someMethod];
}
return vcToBePopped;
}

Can i know in viewWillAppear that it was called after navigationController pop (back button)?

Say I have UIViewController A and B.
User navigates from A to B with a push segue.
Than user presses back button and comes to A.
Now viewWillAppear of A is called. Can I know in the code here that I came from back button (navigationController popTo...) and not by another way? And without writing special code in the B view controller.
hm, maybe you can use self.isMovingToParentViewController in viewWillAppear, see docs, if it is NO then it means the current view controller is already on the navigation stack.
I like to do the following in view controller A:
- (void) viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
if (_popping) {
_popping = false;
NSLog(#"BECAUSE OF POPPING");
} else {
NSLog(#"APPEARING ANOTHER WAY");
}
//keep stack size updated
_stackSize = self.navigationController.viewControllers.count;
....
}
- (void) viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
_popping = self.navigationController.viewControllers.count > _stackSize;
....
}
What you are doing is keeping track of whether your view controller (A) is disappearing because a view controller (B) is being pushed or for another reason. Then (if you did not modify the child view controller order) it should accurately tell you if (A) is appearing because of a pop on the navigation controller.
Add a BOOL property to UIViewController A:
#property (nonatomic) BOOL alreadyAppeared;
Then in your viewWillAppear: method, add:
if (!self.alreadyAppeared) {
self.alreadyAppeared = YES;
// Do here the stuff you wanted to do on first appear
}

Determine if view that appears was pushed or came from back button in navigation bar

Is there a way to tell if a new controller came from a navigation back button or was pushed onto the stack? Id like to reload data only for pushing on the navigation stack, not on a back button press.
As of iOS 5.0 you can do this:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
if (self.isBeingPresented || self.isMovingToParentViewController) {
// "self" is being shown for the 1st time, not because of a "back" button.
}
}
If your push also includes instantiating the view controller, put your push-only logic in viewDidLoad. It will not be called on back because it has already been loaded.
You could implement the UINavigationControllerDelegate and override the `navigationController:didShowViewController:animated:' method. You'll then have to check the returned view controller to make a determination as to whether you came back from the expected view controller.
- (void)navigationController:(UINavigationController*)navigationController didShowViewController:(UIViewController*)viewController animated:(BOOL)animated
{
if (yourPushedViewController == viewController)
{
// Do something
}
}

Resources