0

Main View Controller action that displays the First Modal

@IBAction func button(sender: AnyObject) {
    let sb = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle())
    if let vc = sb.instantiateViewControllerWithIdentifier("VC1") as? FirstViewController {
        vc.modalPresentationStyle = .OverCurrentContext
        vc.modalTransitionStyle = .CrossDissolve
        presentViewController(vc, animated: true, completion: nil)
    }
}

VC1 That has a custom animation to transition to the 2nd view controller

@IBAction func go(sender: AnyObject) {
    guard let vc = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle()).instantiateViewControllerWithIdentifier("VC2") as? SecondViewController else {return}
    vc.transitioningDelegate = transitionManager
    vc.modalPresentationStyle = .CurrentContext
    presentViewController(vc, animated: true, completion: nil)
}

VC2 - just a dismiss call

@IBAction func dismiss(sender: AnyObject) {
    dismissViewControllerAnimated(true, completion: nil)
}

VC2 is the one that doesn't work. Currently, it dismisses itself to VC1. I'd like it to go back to Main without going back first.

Transition Manager

class TransitionManager: NSObject, UIViewControllerAnimatedTransitioning, UIViewControllerTransitioningDelegate {

//    TRANSITION PROTOCOL METHODS
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
    guard let container = transitionContext.containerView() else {return}
    guard let toView = transitionContext.viewForKey(UITransitionContextToViewKey) else {return}
    guard let fromView = transitionContext.viewForKey(UITransitionContextFromViewKey) else {return}

    // set up from 2D transforms that we'll use in the animation
    let width = container.frame.width
    let offScreenRight = CGAffineTransformMakeTranslation(width, 0)
    let offScreenLeft = CGAffineTransformMakeTranslation(-width, 0)

    toView.transform = offScreenRight

    container.addSubview(toView)
    container.addSubview(fromView)

    let duration = self.transitionDuration(transitionContext)



    UIView.animateWithDuration(duration, delay: 0.0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.8, options: .AllowAnimatedContent, animations: {
        fromView.transform = offScreenLeft
        toView.transform = CGAffineTransformIdentity
    }) { finished in
        transitionContext.completeTransition(true)
    }
}

func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
    return 1.4
}


//    DELEGATE METHODS
func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
    return self
}

func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
    return self
}

}

I've tried putting it into a nav controller, dismissing through self.presentingViewController.dismissViewcontroller, and a bunch of other things. I feel I'm missing something very fundamental about View Controllers that will bust this wide open.

Gif of how it shouldn't work:

now not

I'd like 2 to go back to Home without seeing 1 again

EDIT: I've got this to work:

presentingViewController?.presentingViewController?.dismissViewControllerAnimated(false, completion: nil)

But it doesn't feel right and doesn't look right. The main reason I'm doing this style is because the modals in the actual project have translucent backgrounds, so a normal nav controller or regular transition causes them to overlap in ugly ways.

It's also a one way street kind of thing. VC1 should not be seen after VC2 is presented so maybe I'm just thinking about this entirely wrong and a different style component is the best.

2 Answers2

0

Does this get it:

presentingViewController?.presentingViewController?.dismissViewControllerAnimated(true, completion: nil)

edit: presenting, not parent

Lou Zell
  • 5,255
  • 3
  • 28
  • 23
  • It does not. I've got this to work... but it's gross and feels dirty. presentingViewController?.presentingViewController?.dismissViewControllerAnimated(false, completion: nil) – Taylor Smith Jun 03 '16 at 20:22
  • Sure does, was just making sure that's what the stack was like. So here's what I would do: wrap VC 1 in a navigation controller. Main VC _presents_ the nav controller. Then VC 1 _pushes_ VC 2 on. Now VC 2 can get a reference back to the navigation controller with self.presentingViewController – Lou Zell Jun 03 '16 at 20:25
  • Right. When I did that, I lost my custom transition. Nav transitions don't really work because VC1 and VC2 are translucent in the project and when they overlap via the nav push, it looks very bad. Maybe I'm missing something there? Is there some sort of "push this page totally off at the same speed as the other one comes in?" – Taylor Smith Jun 03 '16 at 20:29
  • Ah, I think I get it. Well, going two levels up the VC stack doesn't feel _that_ bad if it's isolated. But implementing custom transitions for UINavigationController is probably more right: http://stackoverflow.com/a/26569703/143447 – Lou Zell Jun 03 '16 at 20:39
  • Just seeing your one-way street comment. Is this some sort of onboarding flow? Maybe try a UIPageViewController? – Lou Zell Jun 03 '16 at 20:40
0

This is typically done with delegation.

Create a protocol:

protocol ModalStackDismiss: class {
    func dismissToModalRoot()
}

Declare your MainViewController as conforming to it.

class MainViewController: UIViewController,ModalStackDismiss

Have MainViewController implement ModalStackDismiss's functions:

func dismissToModalRoot()
{
        self.dismissViewControllerAnimated(true, completion: nil)
}

For each view controller presented modally create a delegate

weak var delegate:ModalStackDismiss?

Then pass the delegate reference to each new view controller presented, for example:

@IBAction func go(sender: AnyObject) {
        guard let vc = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle()).instantiateViewControllerWithIdentifier("VC2") as? SecondViewController else {return}
        vc.transitioningDelegate = transitionManager
        vc.modalPresentationStyle = .CurrentContext
        vc.delegate = self.delegate // when presenting first vc from MainViewController should be vc.delegate = self

        presentViewController(vc, animated: true, completion: nil)
    }

Anytime you want to escape to MainViewController:

@IBAction func dismiss(sender: AnyObject) {
        if let modalDismissDelegate = self.delegate{
            modalDismissDelegate.dismissToModalRoot()
        }
    }

This is quite a bit of work and probably why many try to find ways to avoid delegation, by using things like self.presentingViewController? Another option would be to use NSNotificationCenter. But historically apple has recommended using protocols and delegation for this kind of parent-child message sending.

beyowulf
  • 15,101
  • 2
  • 34
  • 40