14

I have some cleanup that needs to be performed in a shared resource any time one of my view controllers is dismissed/popped/unloaded? This could either be when the user hits the back button on that individual screen or if a call to popToRootViewController is made (in which case, I would ideally be able to clear up every controller that was popped.)

The obvious choice would be to do this in viewDidUnload, but of course, that isn't how unload works. Is there a way to catch all cases to where the ViewController is removed from the stack?

edit:Forgot to mention that I am doing this using Xamarin so that may or may not impact the answers.

cain
  • 1,028
  • 1
  • 12
  • 26
  • Where/when is this shared resource created? What kind of clean up needs to be done? And does this need to be done when any view controller in the app is dismissed or only when an instance of a specific view controller class is dismissed? – rmaddy Oct 27 '14 at 18:53
  • @rmaddy: The resource is added to the viewController when it loads. The cleanup is part of an internal method on the resource that does multiple things that I'm unclear on, but it is supposed to be called. It needs to be called which each individual controller is dismissed. If I hit back on a screen, that screen will need to call the method on its reference to the resource. If I am 4 screens deep and call popToRootViewController the 3 screens that are popped will each need to call the method. – cain Oct 27 '14 at 19:04
  • Then `dealloc`, as mentioned by @SteveMadsen, is probably the best place. – rmaddy Oct 27 '14 at 19:06
  • Yeah, looks like dealloc is the correct answer for Objective-C. Sadly, I don't think it works with Xamarin, so I am still searching for a solution. – cain Oct 27 '14 at 19:28

3 Answers3

22
override func viewDidDisappear(animated: Bool) {
    super.viewDidDisappear(animated)
    if (isBeingDismissed() || isMovingFromParentViewController()) {
        // clean up code here
    }
}

EDIT for swift 4/5

override func viewDidDisappear(_ animated: Bool) {
    super.viewDidDisappear(animated)
    if (isBeingDismissed || isMovingFromParent) {
        // clean up code here
    }
}
Elijah
  • 8,381
  • 2
  • 55
  • 49
Cymric
  • 1,063
  • 1
  • 11
  • 18
  • 3
    Although this is the recommended method (by Apple), in my code it doesn't work when `dismissViewController` called. Maybe that's because I use a custom dismiss transition. – SoftDesigner Nov 03 '16 at 15:32
  • At first you must call super when overriding `viewDidDisappear(_:)`, failure to do so may cause you magical bugs. According to my experiments, while this solution works in most cases, it fails to detect `UINavigationController.popToRootViewController(animated:)` for all but the last view controller in the navigation stack and nested (modal) presentations when you present vc on a presented vc etc. So, as I see it it's rather unreliable + being carefull/disciplined, messy `dealloc`, or all custom container vcs and modal transitions. – MANIAK_dobrii Sep 02 '19 at 11:39
  • @MANIAK_dobrii how would you handle this situation if the vc I am detecting is tabbar->nav controller->vc1->vc2 (detect here when `popToRootViewController` is called)->modal – CyberMew Sep 11 '19 at 03:30
  • @CyberMew I would try to avoid doing it "in a generic way". Instead of trying to detect when UIViewController goes away via UIKit hooks, I'd turn it all around and make this transition explicit. So that I won't have code in vc2 detecting dismissal/popping/whatever, but added something that dismisses (calls `popToRootViewController `) and explicitly notifies vc2. Or, even moved all the required stuff outside of vc and design vc in a way that does not require any bookkeeping like that. Exact solution depends on situation, it's just that you don't have to detect in vc. – MANIAK_dobrii Sep 11 '19 at 14:12
  • Actually this works. @Harris probably you are checking it on the wrong view controller. If you embed it in a NavigationController it will be called with isBeingDismissed=true only on the navigation controller. – Enricoza Dec 21 '20 at 09:57
9

-dealloc is probably your best bet. The view controller will be deallocated when it is popped from the stack, unless you are retaining it elsewhere.

viewWillDisappear: and viewDidDisappear: aren't good choices because they are called any time the view controller is no longer shown, including when it pushes something else on the stack (so it becomes second-from-the-top).

viewDidUnload is no longer used. The system frameworks stopped calling this method as of iOS 6.

Steve Madsen
  • 13,465
  • 4
  • 49
  • 67
4

Building upon @Enricoza's comment, if you do have your UIViewController embedded in a UINavigationController, try this out:

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    
    if ((navigationController?.isBeingDismissed) != nil) {
        // Add clean up code here
    }
}
PGSeattle
  • 41
  • 4