Jonas
Jonas

Reputation: 7885

presentingViewController not working

I want to call a method on the presenting ViewController when I dismiss a popup ViewController. I start the Popup from the 'MainViewController' like this: (With help of EzPopup Library, but I think that shouldn't matter here)

@IBAction func onStartWorkout(_ sender: UIButton) {

    let startWorkoutVC = storyboard!.instantiateViewController(withIdentifier: "CardContent") as! StartWorkout_ViewController

    let popupVC = PopupViewController(contentController: startWorkoutVC, popupWidth: 300, popupHeight: 500)
    popupVC.cornerRadius = 10

    present(popupVC, animated: true)
}

then when I dismiss the Popup I do this:

func dissmissPopup()  {
    if let presenter = presentingViewController as? MainViewController {
        presenter.startWorkout(index: 0, isSNR: true)
    }
    self.dismiss(animated: true, completion: nil)
}

But the method won't be called. How does presentingViewController work and why doesn't my reference to MainViewController work?

Upvotes: 1

Views: 3361

Answers (1)

rmaddy
rmaddy

Reputation: 318804

Assuming dissmissPopup is in your StartWorkout_ViewController class then presentingViewController will be nil because you are not presenting StartWorkout_ViewController, you are presenting PopupViewController. The presentingViewController of PopupViewController should be your MainViewController.

Depending on how PopupViewController is written, you should be able to get what you need by accessing parent (giving you the PopupViewController, and then accessing its presentingViewController.

func dissmissPopup()  {
    if let presenter = parent?.presentingViewController as? MainViewController {
        presenter.startWorkout(index: 0, isSNR: true)
    }

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

Now, having said all of that, don't do this. It's a poor, fragile design. You are hardcoding into StartWorkout_ViewController the knowledge that MainViewController needs some specific method called.

The proper solution is to define a protocol and a delegate. Then StartWorkout_ViewController simply calls the protocol method on its delegate (not caring who it really is). And MainViewController sets itself as StartWorkout_ViewController's delegate and implements the protocol's methods.

This approach eliminates the need for StartWorkout_ViewController to know who presented it or how. It eliminates the need for StartWorkout_ViewController to know to specifically call a method named startWorkout. It also allows other classes to present MainViewController and do whatever it needs when it is dismissed without further hardcoded changes to MainViewController.

Here's a rough outline of implementing this with a protocol and delegate:

StartWorkout_ViewController.swift:

protocol StartWorkoutDelegate: class {
    func complete() // add any necessary parameters
}

class StartWorkout_ViewController: UIViewController {
    weak var delegate: StartWorkoutDelegate?

    func dissmissPopup()  {
        delegate?.complete() // add any necessary parameters

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

MainViewController.swift:

class MainViewController: UIViewController, StartWorkoutDelegate {
    @IBAction func onStartWorkout(_ sender: UIButton) {
        let startWorkoutVC = storyboard!.instantiateViewController(withIdentifier: "CardContent") as! StartWorkout_ViewController
        startWorkoutVC.delegate = self

        let popupVC = PopupViewController(contentController: startWorkoutVC, popupWidth: 300, popupHeight: 500)
        popupVC.cornerRadius = 10

        present(popupVC, animated: true)
    }

    func complete() {
        startWorkout() // add any necessary parameters
    }
}

Upvotes: 3

Related Questions