2

I recreated the question and describe its essence more precisely.

I have two view controllers presented modally on Swift 4, without storyboard (can't use unwind) and without navigation controller

A presents B which presents C.

The reason why we don't use navigation controller, it's because we what simple animation from bottom to top and instead of breaking the standard animation from right to left, we decided to use present.

I would like to dismiss 2 view controllers and go from C to A.

Please, don't mark this question as duplicate before you read my question carefully. I found a tone of similar post, but neither solved my problem. Some of them Objective-C or some of the suggest to use:

self.presentingViewController?.presentingViewController?.dismiss(animated: true, completion: nil)

Or:

self.presentingViewController?.dismiss(animated: false, completion: nil)
self.presentingViewController?.dismiss(animated: true, completion: nil)

It's works, but it create weird animation. It's just delete C and animate dismiss for B:

enter image description here

Expected result:

enter image description here

rmaddy
  • 314,917
  • 42
  • 532
  • 579
Den Andreychuk
  • 420
  • 4
  • 14
  • we don't use storyboard. All code write programmatically. – Den Andreychuk Jan 19 '19 at 10:31
  • https://stackoverflow.com/a/37487975/ covers this problem; this is one of the answers to the first duplicate I provided yesterday. There's almost certainly more coverage of the problem in one of the many other duplicates. Please don't repost; edit [your previous question](https://stackoverflow.com/questions/54262992/how-to-dismiss-2-modal-view-controllers-without-weird-animation) instead. – jscs Jan 19 '19 at 18:00
  • It's Objective-C – Den Andreychuk Jan 19 '19 at 21:07
  • The API is identical in either language. – jscs Jan 19 '19 at 21:08

4 Answers4

4

Idea: you need to dismiss third controller with animation and after dismissing you need to dismiss second without animation. While third controller is being dismissed, second shouldn't be visible.


First, set Presentation style of second view controller to Over Current Context when you're presenting it (since we will need to hide its view when we will dismiss third controller)

let vc2 = VC2()
vc2.modalPresentationStyle = .overCurrentContext
present(vc2, animated: true)

continue with creating callbacks properties for willDismiss and didDismiss inside third controller. This callback will be called before and after you dismiss third controller

class VC3: UIViewController {

    var willDismiss: (() -> Void)?
    var didDismiss:  (() -> Void)?

    @IBAction func dismissButtonPressed(_ sender: UIButton) {

        willDismiss?()

        dismiss(animated: true) {
            self.didDismiss?()
        }
    }

}

then in second view controller in the place where you present third view controller, set third controller's callback properties: declare what happens when third controller will dismiss: you need to hide view of second and then after dismissing third you need to dismiss second without animation (view can stay hidden since view controller will be deinitialized)

class VC2: UIViewController {

    @objc func buttonPressed(_ sender: UIButton) {

        var vc3 = VC3()

        vc3.willDismiss = {
            self.view.isHidden = true
        }

        vc3.didDismiss = {
            self.dismiss(animated: false)
        }

        present(vc3, animated: true)
    }

}

enter image description here


Anyway, second option is using UINavigationController and then just call its method popToViewController(_:animated:).

Robert Dresler
  • 10,580
  • 2
  • 22
  • 40
1

Create a snapshot from the currently visible view and add it as a subview to the first presented view controller. To find that you can simply "loop through" the presenting view controllers and dismiss from the initial one:

@IBAction func dismissViewControllers(_ sender: UIButton) {
    var initialPresentingViewController = self.presentingViewController
    while let previousPresentingViewController = initialPresentingViewController?.presentingViewController {
        initialPresentingViewController = previousPresentingViewController
    }


    if let snapshot = view.snapshotView(afterScreenUpdates: true) {
        initialPresentingViewController?.presentedViewController?.view.addSubview(snapshot)
    }

    initialPresentingViewController?.dismiss(animated: true)
}

This is the result with slow animations enabled for the dismissal: https://www.dropbox.com/s/tjkthftuo9kqhsg/result.mov?dl=0

André Slotta
  • 13,774
  • 2
  • 22
  • 34
0

If you are not using a navigation controller and you want to dismiss all ViewControllers to show the root ViewController (assuming A is the root):

self.view.window?.rootViewController?.dismiss(animated: true, completion: nil)
codeherk
  • 1,609
  • 15
  • 24
0

Use this in button action method. The current VC will dismiss when you dismiss the parent VC. This will dismiss both VCs in single animation.

self.presentingViewController?.presentingViewController?.dismiss(animated: true, completion: nil)