web-dev-qa-db-fra.com

Ignorer plusieurs contrôleurs de vue simultanément

Je fais un jeu en utilisant SpriteKit. J'ai 3 viewControllers: sélectionner le niveau vc, le jeu vc et gagner vc. Une fois le jeu terminé, je veux montrer le win vc, puis si j'appuie sur le bouton OK du win vc, je veux rejeter le win vc ET le jeu vc (sortir deux contrôleurs de vue de la pile). Mais je ne sais pas comment faire parce que si j'appelle

self.dismissViewControllerAnimated(true, completion: {})    

le win vc (haut de la pile) est rejeté, donc je ne sais pas où l'appeler à nouveau pour rejeter le jeu vc. Existe-t-il un moyen de résoudre ce problème sans utiliser le contrôleur de navigation?

Ceci est le 1er VC: (Veuillez faire attention à mes commentaires ci-dessous commençant par "//")

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

C'est le 2ème 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() {...}
}

Et ceci est le 3e 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()
    }

}
35
minsanity

Le commentaire de @Ken Toh a été ce qui a fonctionné pour moi dans cette situation - appelez le renvoi du contrôleur de vue que vous souhaitez afficher après que tout le reste soit rejeté.

Si vous avez une "pile" de 3 contrôleurs de vue présentés A, B et C, où C est en haut, puis appelez A.dismiss(animated: true, completion: nil) rejettera B et C simultanément.

Si vous n'avez pas de référence à la racine de la pile, vous pouvez chaîner quelques accès à presentingViewController pour y accéder. Quelque chose comme ça:

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

Vous pouvez ignorer le contrôleur de présentation de WinVC (GameViewController) dans le bloc d'achèvement:

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

Alternativement, vous pouvez contacter le contrôleur de vue racine et appeler rejeterViewControllerAnimated, qui rejettera les deux contrôleurs de vue modaux dans une seule animation:

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

Vous devriez pouvoir appeler:

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

(Vous devrez peut-être ajouter ? ou ! quelque part - je ne suis pas un développeur Swift)

7
Nils Ziehn

Il y a un enchaînement de déroulement spécial destiné à restaurer la pile de vues sur certains contrôleurs de vue. S'il vous plaît voir ma réponse ici: comment rejeter 2 contrôleur de vue dans Swift ios?

5
Mixaz

J'ai rencontré des problèmes d'animation lors de la tentative de réponse acceptée dans ma candidature. Les vues précédemment présentées clignotaient ou tentaient de s'animer à l'écran. C'était ma solution:

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

     }
3
Aaron Halvorsen

Swift 5 (et éventuellement 4, 3 etc)

presentingViewController?.presentingViewController? n'est pas très élégant et ne fonctionne pas dans certains cas. Utilisez plutôt segues.

Disons que nous avons ViewControllerA, ViewControllerB et ViewControllerC. Nous sommes à ViewControllerC (nous avons atterri ici par ViewControllerA -> ViewControllerB, donc si nous faisons dismiss nous reviendrons à ViewControllerB ). Nous voulons que ViewControllerC retourne directement à ViewControllerA.

Dans ViewControllerA ajoutez l'action suivante dans votre classe ViewController:

@IBAction func unwindToViewControllerA(segue: UIStoryboardSegue) {}

Oui, cette ligne va dans le ViewController du ViewController auquel vous voulez revenir!

Maintenant, vous devez créer une séquence de sortie à partir du storyboard de ViewControllerC (StoryboardC). Allez-y et ouvrez StoryboardC et sélectionnez le storyboard. Maintenez CTRL enfoncé et faites glisser pour quitter comme suit:

enter image description here

Vous recevrez une liste de séquences à choisir, y compris celle que nous venons de créer:

enter image description here

Vous devriez maintenant avoir une séquence, cliquez dessus:

enter image description here

Allez dans l'inspecteur et définissez un identifiant unique: enter image description here

Dans le ViewControllerC au point où vous souhaitez supprimer et revenir à ViewControllerA, procédez comme suit (avec l'ID que nous avons défini dans l'inspecteur précédemment):

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

Ajout à la réponse de Phlippie Bosman, lors de l'appel

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

si vous ne voulez pas voir (quel serait le presentingViewController) vous pouvez faire quelque chose comme

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

Cela semble un peu hacky, mais jusqu'à présent, c'est la seule façon dont j'ai pu donner l'impression que deux contrôleurs de vue sont en train de rejeter à l'unisson.

1
Andrew Foghel

Bien que la réponse de Rafeels soit acceptable. Tout le monde n'utilise pas Segue.

Pour moi, la solution suivante fonctionne le mieux

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)
    }
}
0
Tom

Swift 4.

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