Уволить более одного контроллера одновременно

Я делаю игру, используя SpriteKit. У меня есть 3 viewControllers: выбор уровня vc, игры vc и win vc. После того, как игра окончена, я хочу показать win vc, затем, если я нажму кнопку OK на win vc, я хочу отклонить win vc и игру vc (вытолкнуть из стека два контроллера вида). Но я не знаю, как это сделать, потому что если я позвоню

self.dismissViewControllerAnimated(true, completion: {})    

win vc (вершина стека) отбрасывается, поэтому я не знаю, где его снова вызвать, чтобы закрыть vc игры. Можно ли как-то исправить это без использования контроллера навигации?

Это 1-й ВК: (Пожалуйста, обратите внимание на мои комментарии ниже, начиная с "//")

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

Это второй ВК:

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-й ВК:

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()
    }

}

10 ответов

Решение

Комментарий @Ken Toh был тем, что сработало для меня в этой ситуации - вызовите dismiss от контроллера представления, который вы хотите показать после того, как все остальное будет отклонено.

Если у вас есть "стек" из 3 представленных контроллеров представления A, B а также C, где C на вершине, то звонит A.dismiss(animated: true, completion: nil) уволит B и C одновременно.

Если у вас нет ссылки на корень стека, вы можете связать пару обращений к presentingViewController чтобы добраться до него. Что-то вроде этого:

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

Вы можете отклонить текущий контроллер WinVC (GameViewController) в блоке завершения:

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

В качестве альтернативы, вы можете обратиться к корневому контроллеру представления и вызвать dismissViewControllerAnimated, который отклонит оба модальных контроллера представления в одной анимации:

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

Swift 5 (и, возможно, 4, 3 и т. Д.)

presentingViewController?.presentingViewController? не очень элегантно и не работает в некоторых случаях. Вместо этого используйте segues,

Допустим, у нас есть ViewControllerA, ViewControllerB, а также ViewControllerC, Мы в ViewControllerC (мы приземлились здесь через ViewControllerA -> ViewControllerB так что если мы сделаем dismiss мы вернемся к ViewControllerB). Мы хотим от ViewControllerC прыгнуть прямо к ViewControllerA,

В ViewControllerA добавьте следующее действие в ваш класс ViewController:

@IBAction func unwindToViewControllerA(segue: UIStoryboardSegue) {}

Да, эта строка идет в ViewController ViewController, к которому вы хотите вернуться!

Теперь вам нужно создать выход из ViewControllerC раскадровка (StoryboardC). Иди и открой StoryboardC и выберите раскадровку. Удерживая клавишу CTRL, перетащите для выхода следующим образом:

Вам будет предоставлен список сегментов на выбор, включая тот, который мы только что создали:

Теперь у вас должен быть переход, нажмите на него:

Зайдите в инспектор и установите уникальный идентификатор:

в ViewControllerC в точке, где вы хотите уволить и вернуться к ViewControllerA выполните следующие действия (с идентификатором, который мы установили в инспекторе ранее):

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

Вы должны быть в состоянии позвонить:

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

(Вам может понадобиться добавить ? или же ! где-то - я не стремительный разработчик)

У меня возникли проблемы с анимацией при попытке принять ответ в моем приложении. Ранее представленные виды будут мигать или пытаться анимировать на экране. Это было мое решение:

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

     }

Есть специальный сценарий, предназначенный для отката стека представлений до определенного контроллера представлений. Пожалуйста, посмотрите мой ответ здесь: как отменить 2 вида контроллера в Swift IOS?

Добавление к ответу Phlippie Bosman при звонке

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

если вы не хотите видеть (что было бы presentingViewController) вы можете сделать что-то вроде

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

Это кажется немного странным, но до сих пор это был единственный способ, которым я смог создать впечатление, что два контроллера представления увольняются в унисон.

Хотя ответ Рафилса приемлемый. Не все используют Segue's.

Для меня лучше всего работает следующее решение

if let viewControllers = self.navigationController?.viewControllers {
   let viewControllerArray = viewControllers.filter { 
       $0 is CustomAViewController || $0 is CustomBViewController  }

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

Лучший способ добиться результата OP БЕЗ сбоя анимации (где контроллер промежуточного представления ненадолго отображается во время увольнения) - это встроить контроллер представления A (первый vc) в контроллер навигации, а затем просто поместить строку внутри метод просмотра контроллера C (самый верхний vc).

Как говорится в Apple Doc:

[Это] обновляет или заменяет текущий стек контроллера представления без явного нажатия или извлечения каждого контроллера. Кроме того, этот метод позволяет обновлять набор контроллеров без анимации изменений.

Другими словами, мы просто избавляемся от контроллера промежуточного представления (невидимого в фоновом режиме), так что простой отклонит единственный контроллер представления, оставшийся в стеке (т.е. контроллер представления C).

Swift 4.0

 let presentingViewController = self.presentingViewController               
 presentingViewController?.presentingViewController?.presentingViewController?.dismiss(animated: false, completion: nil)
Другие вопросы по тегам