16
votes

I have two view controllers, MainVC and ModalVC.

When the user taps a button on MainVC, the modal view controller appears.

The user can then tap another button to dismiss it and return to the main one.

I have tried these two methods and they both accomplish the same thing: they dismiss the modal view controller:

//method 1:
//  File: ModalVC.swift
//
@IBAction func dismissTapped() {
     self.dismissViewControllerAnimated(false, completion: nil);
}

That works fine as I said, but consider the other method: using delegation to let the main controller do the dismissing:

// method 2: part A 
// File: ModalVC.swift
// 
protocol ModalVCDelegate {
    func modalVCDismissTapped();
}
...
...
...
var delegat:ModalVCDelegate? = nil;
...
...
@IBAction func dismissTapped() {
    delegate.modalVCDismissTapped();
}

and on the main view controller custom class file:

// method 2: part B
// File: MainVC.swift

class MainVC : UIViewController, ModalVCDelegate {
...
...
    func modalVCDismissTapped() {
        self.dismissViewControllerAnimated(false, completion: nil);
    }
}

Since these two methods do the needful, should I worry about any possible memory leakage?

Any explanation would help

3

3 Answers

20
votes

Using delegation is the best and more flexible way to dismiss view controller.
The purpose of it is that in some future or in some other place in your code you may reuse this VC, but due of some reasons you may not present it modal, but push into navigation stack. So your ModalVC does not know how it was presented, but delegate does.
In this case you can have 2 places in your code

  1. You present it modal and delegate calls

    [self dismiss...]
    
  2. You push it into navigation stack and delegate calls

    [self.navigationController popView...]
    
  3. You add it as a child VC and delegate calls

    [someParentVC removeChild..] 
    

    or any other appropriate workflow to remove it.

0
votes

Modo Ltunzher answer is fine. I personally prefer pass a closure to "child" child will call back "father" when done and ad a bonus, I can pass back values/ results, too.

An example: I present a QRCode that will close and call back when qrcode is recognised:

extension UIViewController {


    func presentQRCode( pushed: Bool, callback: @escaping QRCodeCallBack){

        let qrVC = ScanQRCodeController(nibName: "ScanQRCodeController", bundle: nil)
        qrVC.callback = callback

        if pushed{
            let nc = self.navigationController!
            nc.pushViewController(qrVC, animated: true)

        }else{
            self.present(qrVC, animated: true, completion: nil)
        }

    }


    func dismissQRCode(pushed: Bool){

        if pushed{
            let nc = self.navigationController!
            nc.popViewController(animated: true)
        }else{
            self.dismiss(animated: true, completion: nil)
        }
    }
}

in "father"

   @IBAction func doScanCodeAction(_ sender: UIBarButtonItem) {
        let pushed = true
        self.presentQRCode(pushed: pushed, callback: { (string: String?) in

            if let qrCode = string{
                self.fillFieldsWith(qrCode: qrCode)
            }else{
                #if DEBUG
                print("QR code error")
                #endif
            }

            self.dismissQRCode(pushed: pushed)
        }
        )
    }
0
votes

The presented view controller is not aware that it is being presented at the moment, so it shouldn't know to dismiss itself.

Apple recommends dismissing a presented view controller from the presenting view controller https://developer.apple.com/documentation/uikit/uiviewcontroller/1621505-dismiss

To avoid leaks in your case, always declare you delegate variable as weak. To do that your protocol should inherit from AnyObject.

protocol ModalVCDelegate: AnyObject {
   func modalVCDismissTapped()
}

weak var delegate: ModalVCDelegate?

Another way is to create a closure variable on the presented VC and pass the dismiss action after initializing it on the presenting VC and then call the closure upon an action on the presented VC.

Presenting VC setup for presentation

class PresentingViewController: UIViewController {
  @IBAction func buttonTapped(_ sender: Any) {
     let presentedVC = PresentedViewController()
     presentedVC.modalPresentationStyle = .fullScreen
     presentedVC.onDismissAction = { [weak self] in
       self?.dismiss(animated: true)
     }
     self.present(presentedVC, animated: true, completion: nil)
  }
}

Presented VC setup for dismiss

class PresentedViewController: UIViewController {
  var onDismissAction: (() -> Void)?

  @IBAction func exitButtonTapped(_ sender: Any) {
    onDismissAction?()
  }
}