In my viewDidAppear, how do I know when it's being unwound by a child?
You should be able to use the following to detect in each controller if the exposure of the view controller was as a result of being pushed/presented, or as a result of being exposed as a result of pop/dismiss/unwind.
This may or may be enough for your needs.
- (void) viewDidAppear:(BOOL)animated{ [super viewDidAppear:animated]; // Handle controller being exposed from push/present or pop/dismiss if (self.isMovingToParentViewController || self.isBeingPresented){ // Controller is being pushed on or presented. } else{ // Controller is being shown as result of pop/dismiss/unwind. }}
If you want to know that viewDidAppear
was called because of an unwind segue as being different from a conventional pop/dismiss being called, then you need to add some code to detect that an unwind happened. To do this you could do the following:
For any intermediate controller you want to detect purely an unwind in, add a property of the form:
/** BOOL property which when TRUE indicates an unwind occured. */@property BOOL unwindSeguePerformed;
Then override the unwind segue method canPerformUnwindSegueAction:fromViewController:withSender:
method as follows:
- (BOOL)canPerformUnwindSegueAction:(SEL)action fromViewController:(UIViewController *)fromViewController withSender:(id)sender{ // Set the flag indicating an unwind segue was requested and then return // that we are not interested in performing the unwind action. self.unwindSeguePerformed = TRUE; // We are not interested in performing it, so return NO. The system will // then continue to look backwards through the view controllers for the // controller that will handle it. return NO;}
Now you have a flag to detect an unwind and a means to detect the unwind just before it happens. Then adjust the viewDidAppear
method to include this flag.
- (void) viewDidAppear:(BOOL)animated{ [super viewDidAppear:animated]; // Handle controller being exposed from push/present or pop/dismiss // or an unwind if (self.isMovingToParentViewController || self.isBeingPresented){ // Controller is being pushed on or presented. // Initialize the unwind segue tracking flag. self.unwindSeguePerformed = FALSE; } else if (self.unwindSeguePerformed){ // Controller is being shown as a result of an unwind segue } else{ // Controller is being shown as result of pop/dismiss. }}
Hopefully this meets your requirement.
For docs on handling the unwind segue chain see: https://developer.apple.com/library/ios/technotes/tn2298/_index.html
Here is a simple category on UIViewController that you can use to track whether your presented view controller is in the midst of an unwind segue. I suppose it could be flushed out more but I believe this much works for your case.
To use it you need to register the unwind segue from your unwind action method on the destination view controller:
- (IBAction) prepareForUnwind:(UIStoryboardSegue *)segue{ [self ts_registerUnwindSegue: segue];}
That's it. From your intermediate view controller, you can test if you are in the midst of an unwind segue:
- (void) viewDidAppear:(BOOL)animated{ [super viewDidAppear: animated]; BOOL unwinding = [self ts_isUnwinding]; NSLog( @"%@:%@, unwinding: %@", self.title, NSStringFromSelector(_cmd), unwinding ? @"YES" : @"NO" );}
There's no need to clean anything up; the segue will self-deregister when it ends.
Here's the full category:
@interface UIViewController (unwinding)- (void) ts_registerUnwindSegue: (UIStoryboardSegue*) segue;- (BOOL) ts_isUnwinding;@endstatic NSMapTable* g_viewControllerSegues;@implementation UIViewController (unwinding)- (void) ts_registerUnwindSegue: (UIStoryboardSegue*) segue{ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ g_viewControllerSegues = [NSMapTable weakToWeakObjectsMapTable]; }); for ( UIViewController* vc = segue.sourceViewController ; vc != nil ; vc = vc.presentingViewController ) { [g_viewControllerSegues setObject: segue forKey: vc]; }}- (BOOL) ts_isUnwinding{ return [g_viewControllerSegues objectForKey: [self ts_topMostParentViewController]] != nil;}- (UIViewController *)ts_topMostParentViewController { UIViewController *viewController = self; while (viewController.parentViewController) { viewController = viewController.parentViewController; } return viewController;}@end
Your question was really interesting to me, because I never used IB and segues before (don't judge me for that) and wanted to learn something new. As you described in your comments:
viewDidAppear will be called on
B
whenC
rewinds toA
So I come up with an easy custom solution to this:
protocol ViewControllerSingletonDelegate: class { func viewControllerWillUnwind(viewcontroller: UIViewController, toViewController: UIViewController)}class ViewControllerSingleton { static let sharedInstance = ViewControllerSingleton() private var delegates: [ViewControllerSingletonDelegate] = [] func addDelegate(delegate: ViewControllerSingletonDelegate) { if !self.containsDelegate(delegate) { self.delegates.append(delegate) } } func removeDelegate(delegate: ViewControllerSingletonDelegate) { /* implement any other function by your self :) */ } func containsDelegate(delegate: ViewControllerSingletonDelegate) -> Bool { for aDelegate in self.delegates { if aDelegate === delegate { return true } } return false } func forwardToDelegate(closure: (delegate: ViewControllerSingletonDelegate) -> Void) { for aDelegate in self.delegates { closure(delegate: aDelegate) } }}class SomeViewController: UIViewController, ViewControllerSingletonDelegate { let viewControllerSingleton = ViewControllerSingleton.sharedInstance func someFunction() { // some function where you'll set the delegate self.viewControllerSingleton.addDelegate(self) } /* I assume you have something like this in your code */ @IBAction func unwindToSomeOtherController(unwindSegue: UIStoryboardSegue) { self.viewControllerSingleton.forwardToDelegate { (delegate) -> Void in delegate.viewControllerWillUnwind(unwindSegue.sourceViewController, toViewController: unwindSegue.destinationViewController) } /* do something here */ } // MARK: - ViewControllerSingletonDelegate func viewControllerWillUnwind(viewcontroller: UIViewController, toViewController: UIViewController) { /* do something with the callback */ /* set some flag for example inside your view controller so your viewDidAppear will know what to do */ }}
You also could modify the callback function to return something else, like controller identifier instead the controller itself.
I do everything programmatically, so please don't judge me for that too. ;)
If this code snippet won't help you, I'd still love to see some feedback.