1

Summary of problem

  • I have two View Controllers (VC1 = MainViewController and VC2 = ResultViewController). In VC1 there is a method called endGame(). In this method I want to both dismiss VC1 and present VC2.

  • In VC2 there is a button function called playAgainButton(). In this function I want to both dismiss VC2 and present VC1.

  • when I try to first dismiss VC1 and then present VC2, VC1 cannot present VC2 because VC1 is already dismissed and doesn't exist in the stack.

       dismiss(animated: true) {
         self.present(rvc, animated: true, completion: nil)
       }
    
  • When I try to first present VC2 and then dismiss VC1, then VC2 appears for a 1 second and then immediately disappears.

       present(rvc, animated: true) {
         self.dismiss(animated: true, completion: nil)
       }
    

What I have tried

I have come up with similar problems:

I use the latest swift and Xcode version.

My code

VC1

  // protocol for presenting View Controllers
  protocol VcDelegate: AnyObject {
    func presentVc(vc: UIViewController)
  }

  // The main view controller of the game.
  class MainViewController: UIViewController, UICollectionViewDataSource,
  UICollectionViewDelegateFlowLayout, UICollectionViewDelegate, 
  UIGestureRecognizerDelegate, VcDelegate {

  // MARK: - Protocol functions & properties
  func presentVc(vc: UIViewController) {
    present(vc, animated: false, completion: nil)
  }
  // instance of VcDelegate protocol
  weak var mvcDelegate: VcDelegate?

  // MARK: - Properties
  // the level of the game
  var level: Int = 0
  // the player's score
  var score: Int = 0

  /// Terminate the game for the next level and show results page
  private func gameOver() {
    // present the resultViewController
    if let rvc = storyboard?.instantiateViewController(withIdentifier: "ResultViewController")   as? ResultViewController {
      rvc.resultLevel = level
      rvc.resultScore = score
      rvc.rvcDelegate = self
  
      // dismiss MainViewController
      dismiss(animated: true) {
        // present ResultViewController by using instance of VcDelegate protocol
        self.mvcDelegate?.presentVc(vc: rvc)
      }
     }
   }
  }

VC2

// This page is viewed after a level is finished. It shows level results and play again  button.
class ResultViewController: UIViewController, VcDelegate {

  // MARK: - Protocol functions & properties
  func presentVc(vc: UIViewController) {
    present(vc, animated: false, completion: nil)
  }
  weak var rvcDelegate: VcDelegate?

  // MARK: - Properties

  // variable showing game level
  var resultLevel = 0
  // variable showing current score
  var resultScore = 0

  /// When play again button is tapped a new game starts (a new mainViewController is presented)
  @IBAction func playAgainButton(_ sender: UIButton) {
    // present a new MainViewController
    if let mvc = storyboard?.instantiateViewController(withIdentifier: "MainViewController") as? MainViewController {
      mvc.level = resultLevel
      mvc.score = resultScore
      mvc.mvcDelegate = self
  
      // dismiss ResultViewController
      dismiss(animated: true) { 
        // present MainViewController by using instance of VcDelegate protocol
        self.rvcDelegate?.presentVc(vc: mvc)
      }
    }
  }

 }

1 Answers1

1

The problem is that when you create the VC1 and you pass self as mvcDelegate, you actually passing VC2 which is about to be dismissed and after the dismiss VC2 cannot present any view controller.

You probably need to pass the delegate of the one view controller to the other before you present it:

// Pass your delegate to the other view controller delegate
mvc.mvcDelegate = rvcDelegate
// dismiss ResultViewController
dismiss(animated: true) {
    // present MainViewController by using instance of VcDelegate protocol
    self.rvcDelegate?.presentVc(vc: mvc)
}

And the other way around:

// Pass your delegate to the other view controller delegate
rvc.rvcDelegate = mvcDelegate
// dismiss MainViewController
dismiss(animated: true) {
    // present ResultViewController by using instance of VcDelegate protocol
    self.mvcDelegate?.presentVc(vc: rvc)
}

As mentioned in the comments VC1 is the rootViewController of the application window. So, to achieve what you are looking for, you need to replace the rootViewController.

In MainViewController:

// Change rootViewController of the view's window with ResultViewController
view.window?.rootViewController = rvc

And the same in ResultViewController:

// Change rootViewController of the view's window with MainViewController
view.window?.rootViewController = mvc
gcharita
  • 7,729
  • 3
  • 20
  • 37
  • I have tried your solution but it is not working. VC1 is dismissed but it still doesn't present VC2 – Jaiden Ryder Sep 15 '20 at 05:26
  • @JaidenRyder when you are initially present VC1 do you set it's `mvcDelegate` property? The important thing here is that none of the 2 view controllers can present each other. Because both will be dismissed. – gcharita Sep 15 '20 at 07:51
  • in fact the app starts with VC1. And I set mvcDelegate property only in endGame() method. And I do exactly the same thing in both view controllers. In VC2 I can dismiss and present VC1 in the same method, but I can't do the same when I am in VC1. When I am in VC1 I can dismiss VC1, I can present VC2. But 'self.mvcDelegate?.presentVc(vc: rvc)' code doesn't return anything. – Jaiden Ryder Sep 15 '20 at 10:14
  • @JaidenRyder so, VC1 is not presented, is the `rootViewController` of your application Window. That changes a thing or two. – gcharita Sep 15 '20 at 10:21
  • @JaidenRyder Happy to help. [What should I do when someone answers my question?](https://stackoverflow.com/help/someone-answers) – gcharita Sep 15 '20 at 11:07