iosswiftuiviewcontrollerpresentmodalviewcontrollerdismissviewcontroller

Dismiss a View Controller and present a new one in the same method - swift


Summary of problem

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

 }

Solution

  • 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