I have the following set up:
The red controller has a method
- (IBAction)unwindToRed:(UISegue *)segue
and a
- (UIStoryboardSegue *)segueForUnwindingToViewController:(UIViewController *)toViewController fromViewController:(UIViewController *)fromViewController identifier:(NSString *)identifier
which returns a custom segue for this event.
In my custom segue I have the following in the perform code:
- (void)perform
{
// Just helpers to get the controllers from the segue
UIViewController *destination = self.destinationViewController;
UIViewController *source = self.sourceViewController;
// Setting transitioning delegate for custom transitions
destination.transitioningDelegate = self;
source.transitioningDelegate = self;
destination.modalTransitionStyle = UIModalPresentationCustom;
source.modalPresentationStyle = UIModalPresentationCurrentContext;
// When I am transitioning to a new one
if(!self.reversed)
{
[source presentViewController:destination animated:YES completion:nil];
}
// Or reversing from a current one
else
{
[source dismissViewControllerAnimated:YES completion:nil];
}
}
As you can see I have setup a transition delegate which which in turn calls
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
on an animation controller where I apply my custom animation. But now when I tap Unwind to Red on the yellow view, it only "unwinds" to its parent. (e.g the blue one).
When I leave the custom segue out of the equation it works just fine. It goes al the way to the top.
Now I think this has to do with the
[source dismissViewControllerAnimated:YES];
line, since that usually just goes "one up" my question is I think, what should I call there so it goes all the way up to red when needed? Yellow is a modal overlay. So "pop too root" would not work.
Specifing a Custom Segue
The solution was to use a custom segue for the unwind. Unfortunately you cannot specify a custom segue for an unwind in interface builder like you can on a normal segue. You have to specify it with code.
There is a function on UIViewController
that you can override called segueForUnwindingToViewController
which specifies which segue to use for the unwind. The tricky part is knowing which view controller to override this function on. The answer is to do it on the parent of the view controllers you are transitioning to/from. In my case it is the UINavigationController
. The navigation controller is the container view controller for the first and third view controller. If I had embedded child view controllers in my views, then the answer would not be the navigation controller but the parent view controller.
Create a custom navigation controller by inheriting from UINavigationController
. Then make sure to set the custom class of your navigation controller in interface builder. Now you can override the segueForUnwindingToViewController
function. Here is a minimal implementation.
class MyNavigationController: UINavigationController {
override func segueForUnwindingToViewController(toViewController: UIViewController,
fromViewController: UIViewController,
identifier: String?) -> UIStoryboardSegue {
return UIStoryboardSegue(identifier: identifier, source: fromViewController, destination: toViewController)
}
}
Adding Animation to Custom Segue
override func segueForUnwindingToViewController(toViewController: UIViewController,
fromViewController: UIViewController,
identifier: String?) -> UIStoryboardSegue {
return UIStoryboardSegue(identifier: identifier, source: fromViewController, destination: toViewController) {
let fromView = fromViewController.view
let toView = toViewController.view
if let containerView = fromView.superview {
let initialFrame = fromView.frame
var offscreenRect = initialFrame
offscreenRect.origin.x -= CGRectGetWidth(initialFrame)
toView.frame = offscreenRect
containerView.addSubview(toView)
// Being explicit with the types NSTimeInterval and CGFloat are important
// otherwise the swift compiler will complain
let duration: NSTimeInterval = 1.0
let delay: NSTimeInterval = 0.0
let options = UIViewAnimationOptions.CurveEaseInOut
let damping: CGFloat = 0.5
let velocity: CGFloat = 4.0
UIView.animateWithDuration(duration, delay: delay, usingSpringWithDamping: damping,
initialSpringVelocity: velocity, options: options, animations: {
toView.frame = initialFrame
}, completion: { finished in
toView.removeFromSuperview()
if let navController = toViewController.navigationController {
navController.popToViewController(toViewController, animated: false)
}
})
}
}
}
Details are from this post
You want to use the Navigation controller's popToViewController:
[source.navigationController popToViewController:destination animated:YES];
or, use animated:NO
and put the whole thing in an animation block: e.g.:
UIViewAnimationOptions animation = UIViewAnimationOptionTransitionFlipFromRight | UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionAllowAnimatedContent;
UIViewController *source = (UIViewController *) self.sourceViewController;
UIViewController *dest = (UIViewController *) self.destinationViewController;
[UIView transitionWithView:src.navigationController.view duration:0.4
options: animation
animations:^{
[source.navigationController popToViewController:dest animated:NO];
}
completion:^(BOOL finished) {
}];