In my viewDidAppear, how do I know when it's being unwound by a child? In my viewDidAppear, how do I know when it's being unwound by a child? swift swift

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 when C rewinds to A

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.