41

I am presenting a view controller from a view controller called HomeController like so:

let viewController = self.storyboard?.instantiateViewController(withIdentifier: "LoginController") as! LoginController

let navigationController: UINavigationController = UINavigationController(rootViewController: viewController)

present(navigationController, animated: true, completion: nil)

In the presented view controller LoginController at some point gets dismissed:

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

But when it comes back to HomeController it is not calling viewWillAppear, I really need to check on condition on HomeController when it comes back to it, So how can I call viewWillAppear when LoginController dismisses the view?

mfaani
  • 33,269
  • 19
  • 164
  • 293
user979331
  • 11,039
  • 73
  • 223
  • 418

6 Answers6

46

You need to set the correct presentationStyle. If you want that your presentedController will be fullScreen and call it the previous viewWillAppear, then you can use ".fullScreen"

let viewController = self.storyboard?.instantiateViewController(withIdentifier: "LoginController") as! LoginController

let navigationController: UINavigationController = UINavigationController(rootViewController: viewController)

navigationController.modalPresentationStyle = .fullScreen

present(navigationController, animated: true, completion: nil)
Kevinosaurio
  • 1,952
  • 2
  • 15
  • 18
  • 1
    I tried this viewWillAppear is not being called when dimissing LoginController – user979331 Jun 28 '18 at 18:44
  • 1
    @user979331 sorry my Bad is *.fullScreen* – Kevinosaurio Jun 28 '18 at 18:46
  • 2
    You can click on the segue in the storyboard and under the Attributes Inspector, change the Presentation to Full Screen. For me, it is easier than writing the code. – K. Law Mar 11 '19 at 14:08
  • 1
    I know this is an old question but still relevant to keep it fresh. I had a issue with not ALL my segues being fullscreen modal presentation. It was driving me crazy but it makes sense. All view controllers presented modally must be full screen otherwise view will appear will not be called at all. Even if you miss one, the last view when dismissed to root, will not call view will appear. – Julian Silvestri Apr 06 '20 at 20:13
  • Is there any way to call willAppear after dismiss viewController if use "PresentationStyle = .overCurrentContext" ? Because I need transparent model view so can not use modalPresentationStyle = .fullScreen. – Dipak Apr 22 '21 at 07:07
  • @Dipak you can use PresentationStyle = .currentContext – Kevinosaurio Apr 23 '21 at 01:28
  • @Kevinosaurio after using PresentationStyle = .currentContex, viewWillAppear get called but lost transparency. Transparency is my basic requirement so can not use currentContex – Dipak Apr 27 '21 at 10:06
  • I works. Setting by code helped me to check the solution, also you can change it from IB in the Navigation Controller > Attributes Inspector. – tontonCD Aug 11 '21 at 14:29
16

If presented viewController is half screen then you will have to Call the viewWillAppear of presenting viewController manually in side presented view controller's viewWillDisappear. Add following code to your Presented view controller.

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    presentingViewController?.viewWillDisappear(true)
}    

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    presentingViewController?.viewWillAppear(true)
}

Note: you must have to call 'presentingViewController?.viewWillDisappear(true)' to get your Presenting view controllers viewWillAppear execute everytime.

VIkas
  • 161
  • 1
  • 3
9

Changing presentation style to .fullScreen works but changes the appearance of the presented view Controller. If u want to avoid that u can override the viewWillDisappear method in the presented viewcontroller and inside it add presentingViewController?.viewWillAppear(true).

Example:

 class ViewControllerA: UIViewController {
 
      override func viewDidLoad() {
          super.viewDidLoad()
          
          //put the presenting action wherever you want

          let vc = ViewControllerB()
          navigationController.present(vc, animated: true)
      }

      override func viewWillAppear(_ animated: Bool) {
           super.viewWillAppear(animated) 
           //refresh Whatever 
      }
 }

 class ViewControllerB: UIViewController {
 
      override func viewDidLoad() {
          super.viewDidLoad()
      }

      override func viewWillDisappear(_ animated: Bool) {
          super.viewWillDisappear(animated)

          //this will call the viewWillAppear method from ViewControllerA  

          presentingViewController?.viewWillAppear(true)
      }
 }
  • 1
    Good but only works the first time – Paul Bénéteau Jan 29 '22 at 20:01
  • It's also worth noting with this approach that if ViewControllerB presents ViewControllerC, then the viewWillDisappear() method is also called when ViewControllerC appears, which might not be desirable. – Derek Lee Nov 04 '22 at 08:52
  • This solution throws errors in the console and break animation in UINavigationControllers - see my solution here https://stackoverflow.com/a/74618304/158548 – Niall Mccormack Nov 29 '22 at 17:57
4

None of these solutions seemed satisfactory, and indeed the solutions that suggested calling presentingViewController?.viewWillAppear(true) actually break animations in navigations controllers and throws warnings from UIKit in the console.

The correct way is to use the built in methods in UIKit, as suggested by the warnings! These will call all the respective methods viewWill/DidAppear in the presenting class.

Note I have an if statement checking if the modelPresentationStyle is equal to pageSheet as this is the value that recent releases of iOS set the default presentation style to. We do not want to interfere with other presentation styles as they may already call viewWill/DidAppear and it should not be called twice.

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)

    // view lifecycle events are not passed down to the presentingViewController if in pageSheet style (and maybe others)
    if modalPresentationStyle == .pageSheet {
        presentingViewController?.beginAppearanceTransition(false, animated: animated)
    }
}

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

    // view lifecycle events are not passed down to the presentingViewController if in pageSheet style (and maybe others)
    if modalPresentationStyle == .pageSheet {
        presentingViewController?.endAppearanceTransition()
    }
}

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)

    // view lifecycle events are not passed down to the presentingViewController if in pageSheet style (and maybe others)
    if modalPresentationStyle == .pageSheet {
        presentingViewController?.beginAppearanceTransition(true, animated: animated)
    }
}

override func viewDidDisappear(_ animated: Bool) {
    super.viewDidDisappear(animated)

    // view lifecycle events are not passed down to the presentingViewController if in pageSheet style (and maybe others)
    if modalPresentationStyle == .pageSheet {
        presentingViewController?.endAppearanceTransition()
    }
}
Eric Aya
  • 69,473
  • 35
  • 181
  • 253
Niall Mccormack
  • 1,314
  • 13
  • 19
  • 3
    `presentingViewController` in `viewDidDisappear` always nil. you may add `private weak var presentingViewControllerBeforeDisappear: UIViewController?`, and set `presentingViewControllerBeforeDisappear = presentingViewController` before `presentingViewController?.beginAppearanceTransition(true, animated: animated)` in `viewWillDisappear`, and then set `presentingViewControllerBeforeDisappear?.endAppearanceTransition()` in `viewDidDisappear` – Leo Dec 01 '22 at 14:19
  • Thank you Leo for you comment it do solve the issue for the viewDidDisappear case. @Eric can you update the answer to include Leo fix? – Iosif May 09 '23 at 07:06
3

In iOS 13 , if you are presenting a view controller and when you are coming back viewWillAppear doesn't get called . I have changed it from present to push view controller and methods are getting called now .

Sateesh Pasala
  • 772
  • 8
  • 15
1

Swift 5. Work for me. Maybe all forget about delegate.

First Controller

let calendarVC = CalendarVC()
calendarVC.delegate = self
calendarVC.modalTransitionStyle = .coverVertical
present(calendarVC, animated: true, completion: nil)

Present controller

Add Delegate ........

override func viewWillDisappear(_ animated: Bool) {
  super.viewWillDisappear(animated)
        delegate?.closeState()
 }

Work when swipe and dismiss button. Have a good day )

Evgeniy
  • 96
  • 1
  • 4