同时关闭多个视图控制器

Dismiss more than one view controller simultaneously

我正在使用 SpriteKit 制作游戏。 我有 3 个视图控制器:选择级别 vc、游戏 vc 和获胜 vc。 游戏结束后,我想显示胜利 vc,然后如果我在胜利 vc 上按确定按钮,我想关闭胜利 vc 和游戏 vc(从堆栈中弹出两个视图控制器)。但我不知道该怎么做,因为如果我调用

self.dismissViewControllerAnimated(true, completion: {})    

胜利 vc(堆栈顶部)被取消,所以我不知道在哪里再次调用它来取消游戏 vc。 有什么办法可以在不使用导航控制器的情况下解决这个问题吗?

这是第一个VC:(请注意我下面以“//”开头的评论)

class SelectLevelViewController: UIViewController { // I implemented a UIButton on its storyboard, and its segue shows GameViewController
    override func viewDidLoad() {
        super.viewDidLoad()
    }
}

这是第二个VC:

class GameViewController: UIViewController, UIPopoverPresentationControllerDelegate {
    var scene: GameScene!
    var stage: Stage!

    var startTime = NSTimeInterval()
    var timer = NSTimer()
    var seconds: Double = 0
    var timeStopped = false

    var score = 0

    @IBOutlet weak var targetLabel: UILabel!
    @IBOutlet var displayTimeLabel: UILabel!
    @IBOutlet weak var scoreLabel: UILabel!
    @IBOutlet weak var gameOverPanel: UIImageView!
    @IBOutlet weak var shuffleButton: UIButton!
    @IBOutlet weak var msNum: UILabel!

    var mapNum = Int()
    var stageNum = Int()

    var tapGestureRecognizer: UITapGestureRecognizer!

    override func viewDidLoad() {
        super.viewDidLoad()

        let skView = view as! SKView
        skView.multipleTouchEnabled = false

        scene = GameScene(size: skView.bounds.size)
        scene.scaleMode = .AspectFill
        msNum.text = "\(mapNum) - \(stageNum)"

        stage = Stage(filename: "Map_0_Stage_1")
        scene.stage = stage
        scene.addTiles()
        scene.swipeHandler = handleSwipe

        gameOverPanel.hidden = true
        shuffleButton.hidden = true

        skView.presentScene(scene)

        Sound.backgroundMusic.play()

        beginGame()
    }

    func beginGame() {
        displayTimeLabel.text = String(format: "%ld", stage.maximumTime)
        score = 0
        updateLabels()

        stage.resetComboMultiplier()

        scene.animateBeginGame() {
            self.shuffleButton.hidden = false
        }

        shuffle()

        startTiming()
    }

    func showWin() {
        gameOverPanel.hidden = false
        scene.userInteractionEnabled = false
        shuffleButton.hidden = true

        scene.animateGameOver() {
            self.tapGestureRecognizer = UITapGestureRecognizer(target: self, action: "hideWin")
            self.view.addGestureRecognizer(self.tapGestureRecognizer)
        }
    }

    func hideWin() {
        view.removeGestureRecognizer(tapGestureRecognizer)
        tapGestureRecognizer = nil

        gameOverPanel.hidden = true
        scene.userInteractionEnabled = true

        self.performSegueWithIdentifier("win", sender: self) // this segue shows WinVC but idk where to dismiss this GameVC after WinVC gets dismissed...
    }

    func shuffle() {...}
    func startTiming() {...}
}

这是第 3 个 VC:

class WinVC: UIViewController {

    @IBOutlet weak var awardResult: UILabel!

    @IBAction func dismissVC(sender: UIButton) {
        self.dismissViewControllerAnimated(true, completion: {}) // dismissing WinVC here when this button is clicked
    }

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

}

您应该可以拨打电话:

self.presentingViewController.dismissViewControllerAnimated(true, completion: {});

(您可能需要在某处添加 ?! - 我不是 swift 开发人员)

您可以在完成块中关闭 WinVC 的呈现控制器 (GameViewController):

let presentingViewController = self.presentingViewController
self.dismissViewControllerAnimated(false, completion: {
  presentingViewController?.dismissViewControllerAnimated(true, completion: {})
})

或者,您可以访问根视图控制器并调用 dismissViewControllerAnimated,这将在单个动画中关闭两个模态视图控制器:

self.presentingViewController?.presentingViewController?.dismissViewControllerAnimated(true, completion: {})

有特殊的 unwind segue 旨在将视图堆栈回滚到特定视图控制器。请在这里查看我的回答:how to dismiss 2 view controller in swift ios?

@Ken Toh 的评论在这种情况下对我有用——在其他所有内容都被取消后,从要显示的视图控制器中调用取消。

如果您有一个 "stack" 的 3 个视图控制器 ABC,其中 C 位于顶部,然后调用 A.dismiss(animated: true, completion: nil) 将同时解散 B 和 C。

如果您没有对堆栈根的引用,您可以将几个访问链接到 presentingViewController 以到达它。像这样:

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

在我的应用程序中尝试接受的答案时,我遇到了一些动画问题。先前呈现的视图会闪烁或尝试在屏幕上显示动画。这是我的解决方案:

     if let first = presentingViewController,
        let second = first.presentingViewController,
            let third = second.presentingViewController {
                second.view.isHidden = true
                first.view.isHidden = true
                    third.dismiss(animated: true)

     }

Swift 4.0

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

在调用

时添加到 Phlippie Bosman 的回答中
self.presentingViewController?.presentingViewController?.dismiss(animated: true, completion: nil)

如果你不想看到(presentingViewController 是什么)你可以做类似

self.presentingViewController?.view.addSubview(self.view)

这看起来有点老套,但到目前为止,这是我唯一能让两个视图控制器看起来一致的方法。

Swift 5(可能还有 4、3 等)

presentingViewController?.presentingViewController? 不是很优雅,在某些情况下不起作用。相反,使用 segues.

假设我们有 ViewControllerAViewControllerBViewControllerC。我们在 ViewControllerC(我们通过 ViewControllerA -> ViewControllerB 到达这里,所以如果我们到达 dismiss,我们将返回 ViewControllerB)。我们希望从 ViewControllerC 直接跳回到 ViewControllerA

ViewControllerA 中添加以下操作 ViewController class:

@IBAction func unwindToViewControllerA(segue: UIStoryboardSegue) {}

是的,这行在你想返回的 ViewController 的 ViewController 中!

现在,您需要从 ViewControllerC 的故事板 (StoryboardC) 创建一个退出转场。继续并打开故事板 StoryboardC 和 select。按住CTRL并拖动退出如下:

您将获得一系列可供选择的转场,包括我们刚刚创建的转场:

你现在应该有一个 segue,点击它:

进入检查器并设置一个唯一的 id:

ViewControllerC 中您想要关闭并 return 回到 ViewControllerA 的位置,执行以下操作(使用我们之前在检查器中设置的 ID):

self.performSegue(withIdentifier: "yourIdHere", sender: self)

尽管 Rafeels 的回答是可以接受的。不是每个人都使用 Segue 的。

对我来说,以下解决方案效果最好

if let viewControllers = self.navigationController?.viewControllers {
   let viewControllerArray = viewControllers.filter { 
       [=10=] is CustomAViewController || [=10=] is CustomBViewController  }

    DispatchQueue.main.async {
      self.navigationController?.setViewControllers(viewControllerArray,
                                                    animated: true)
    }
}

实现 OP 结果的最佳方法没有 动画故障(中间视图控制器在关闭期间短暂显示)是嵌入视图控制器 A(第一个 vc) 在导航控制器中,然后只需将行 self.navigationController!.setViewControllers([self], animated: false) 放在视图控制器 C 的 ViewDidAppear 方法中(最上面的 vc)。

如 Apple 文档所述:

[This] updates or replace the current view controller stack without pushing or popping each controller explicitly. In addition, this method lets you update the set of controllers without animating the changes

换句话说,我们只是简单地摆脱了中间视图控制器(在后台不可见),这样一个简单的 self.dismiss(animated: true) 将关闭保留在堆栈中的唯一视图控制器(即视图控制器) C).