viewcontroller lifecycle in UIWindowSceneDelegate

I've made the architectural changes following best practices to support multiple windows in map. In general, it seems to be working fine except for one general thing. It seems that some the view controller life cycle methods (i.e.: viewWillDisappear, viewDidDisappear) never get called. I realize you can accomplish all that these methods do with the proper delegates in UIWindowSceneDelegate but its not clear to me that this is the way it's supposed to be.


In the documentation it's clear that many methods in AppDelegate lifecycle are now replaced by their corresponding UIWindowSceneDelegate methods but I haven't found anything that indicates view controller life cycle.


It seems that the initial methods are still called (i.e.: viewWillAppear, viewDidAppear) but not trailing methods (i.e.: viewWillDisappear, viewDidDisappear).


Is that how it's suppose to work or am I doing something wrong?


Thanks!

Replies

I have tested in an app with a scenedelegate.


In the UIViewController, I included:


    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        print("will disappear")
    }


It gets called when I close the view which was presented through a segue.


How do you present this "next" view ?


Note.

In appDelegate, I have:

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { }

    func applicationWillEnterForeground(_ application: UIApplication) { }

    func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { }

    func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { }

And in SceneDelegate:


    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { }

    func sceneDidDisconnect(_ scene: UIScene) {
        guard let _ = (scene as? UIWindowScene) else { return }
    }

    func sceneDidBecomeActive(_ scene: UIScene) { }

    func sceneWillResignActive(_ scene: UIScene) { }

    func sceneWillEnterForeground(_ scene: UIScene) { }

In my scene delegate, I instantiate the view controller which I then embed in a Navgiation controller (I need a nav bar); see lines 22, 24 and 40 below.


  func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    guard let windowScene = (scene as? UIWindowScene) else { return }
    
    let rootViewController: UIViewController
    
    if let shortcutItem = connectionOptions.shortcutItem {
      dPrint("\(#function) \(shortcutItem)")
    }
    
    closeWindow = nil
    
    let persistentId = scene.session.persistentIdentifier
    if let activity = connectionOptions.userActivities.first ?? session.stateRestorationActivity,
      let identifier = getIdentifier(from: activity) {
      scene.title = activity.title ?? identifier
      if isMultiScenesSupported {
        closeWindow = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(closeWindowAction))
        closeWindow?.setAccessibilityProperties("CloseWindow")
      }
      
      // The identifier corresponds to the storyboard ID for a detail viewcontroller.
      let detailViewController = instantiateViewController(with: identifier)
      // Embed in a navigation controller to provide title and Done button.
      rootViewController = UINavigationController(rootViewController: detailViewController)
      detailViewController.restorable?.restore(from: activity, isStandalone: isMultiScenesSupported, persistentId: persistentId)
    } else {
      let identifier = AppMainFeatureData.calculator.storyboardIdentifier
      // The identifier corresponds to the storyboard ID for a detail viewcontroller.
      let detailViewController = instantiateViewController(with: identifier)
      // Embed in a navigation controller to provide title and Done button.
      rootViewController = UINavigationController(rootViewController: detailViewController)
      detailViewController.restorable?.restore(from: NSUserActivity(activityType: AppMainFeatureData.calculator.activityType), isStandalone: isMultiScenesSupported, persistentId: persistentId)
    }
    
    window = UIWindow(windowScene: windowScene)
    window?.rootViewController = rootViewController
    window?.makeKeyAndVisible()
  }
  
  public func instantiateViewController(with identifier: String) -> UIViewController {
    UIStoryboard(name: storyboardName, bundle: nil).instantiateViewController(withIdentifier: identifier)
  }

I tested by creating the child views in storyboard.


Works OK as well.


I cannot test your code as sevaral var or functions are not declared:

- dPrint

- closeWindow

- getIdentifier

- isMultiSceneSupported

- …

Sorry about that ...

I should have also also mentioned that the lack of calls to trailing view controller life cycle methods appears to be when I create new scenes. That's why I posted that code.


I have all those SceneDelegate methods you mentioned above implemented and they work fine as far scene life cycle is concerned.


I need to support iOS < 13 and I typically add/remove observers in viewWillAppear()/viewWillDisappear(). I can certainly handle the removing of observers in one of the SceneDelegate methods. But I just find it strange that the view controller life cycle methods area called when the view is appearing (creating a new scence) but now when the view is dissapearing (removing a scene in Expose).


Are you creating a new scene and are able to see calls to viewWillDisappear() and/or viewDidDisappear() when you disconnect the scene? By, for example, swiping up in Expose?


Thanks!

To support older iOS version, you have to test iOS:


Such as:


1. In Scene Delegate

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?

    @available(iOS 13.0, *)
    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        guard let _ = (scene as? UIWindowScene) else { return }
    }

What I usually do :


2. In AppDelegate, I create a window property as well


@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    // MARK: - Application lifecycle

    var window: UIWindow?   // For iOS 12

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
          // …
     }

As well as for scene related in AppDelegate:



    @available(iOS 13.0, *)
    func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
     return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
    }

    @available(iOS 13.0, *)
    func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) {
     }

Thanks for the reply. I have all that!


I guess I still have one basic question: are ViewController life cycle methods viewWillDisappear() and viewDidDisappear() still supposed to be called in iOS 13 when supporting scenes and closing a scene?


If that's the case it's not happening in my app and I'll dig into "why".


I just wanted to make sure that is the case before chasing my tail too long.


Thank!

Yes, viewWillDisappear is fired on iOS 13 with scene management in my sample code.