Scene Delegate vs AppDelegate

Earlier to Xcode 11/iOS 13, when you create a new project, some default files like AppDelegate.swift, ViewController.swift and a Main.storyboard and few other files were created but from Xcode 11 you might have noticed that along with the default files like above, a new file SceneDelegate.swift is also created.

If you’re still confused about what is app life cycle and AppDelegate. Please read my blog application life cycle in iOS.

I hope by now you’re aware of AppDelegate, how app delegate interact to user event and app life cycle. Now it’s time to discuss why this new file is created and how to use that scene delegate in your application development.

Advantages of SceneDelegate

What are the changes with the new scene API?

To understand the changes to the application lifecycle you need to know about the following core objects of the new scene API.

UIScene and UISceneDelegateClassName

UIWindowScene

UIWindowSceneDelegate

UISceneSession

UISceneConfiguration

The most important information is kept inside the items in the Application Session Role array is

Changes to the AppDelegate and it’s responsibilities

func application(_:didFinishLaunchingWithOptions:) -> Bool

func application(_:configurationForConnecting:options:) -> UISceneConfiguration

func application(_:didDiscardSceneSessions:)

In addition to these default methods, AppDelegate can still be used to open URLs, catch memory warnings, detect when your app will terminate, detect when a user has registered for remote notifications and more.

UISceneConfiguration

There are several methods in the SceneDelegate.swift file by default:

1/ sceneDidDisconnect(_:) is called when a scene has been disconnected from the app (Note that it can reconnect later on.)

2/ sceneDidBecomeActive(_:) is called when the user starts interacting with a scene, such as selecting it from the app switcher

3/ sceneWillResignActive(_:) is called when the user stops interacting with a scene, for example by switching to another scene

4/ sceneWillEnterForeground(_:) is called when a scene enters the foreground, i.e. starts or resumes from a background state

5/ sceneDidEnterBackground(_:) is called when a scene enters the background, i.e. the app is minimised but still present in the background.

Life Cycle of Scene

Adding multiple scene programatically

Before starting make sure the device Supports multiple windows.

In this blog, i’m going to explain how to add multiple scene by considering bellow example. Here I’m taking two viewcontroller which are:

func didTapCat() {

  let activity = NSUserActivity(activityType: "viewCatDetail")

  //Add targetContentIdentifier
  activity.targetContentIdentifier = "new"

  let session = UIApplication.shared.openSessions.first { openSession in
    guard let sessionActivity = openSession.scene?.userActivity,
          let targetContentIdentifier = sessionActivity.targetContentIdentifier else {
      return false
    }
    return targetContentIdentifier == activity.targetContentIdentifier
  }

  UIApplication.shared.requestSceneSessionActivation(session, userActivity: activity, options: nil, errorHandler: nil)

}
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
  if let activity = options.userActivities.first, activity.activityType == "viewCatDetail" {
    return UISceneConfiguration(name: "Cat Detail", sessionRole: connectingSceneSession.role)
  }
  return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
  if let activity = connectionOptions.userActivities.first ?? session.stateRestorationActivity,
    let identifier = activity.targetContentIdentifier {
      //load your view into widow.
  }
}

targetContentIdentifier is a string that identifies the content of the NSUserActivity, for matching against existing documents when re-opening to see if they are the same.

or we can drag and drop one scene above the other

How can we add multiple story board in scene delegate?

Adding a scene delegate to an existing project

Step 1:

Add a UIApplicationSceneManifest to the info.plist. There are two way you can add it

1/ Click on your project in the project navigator -> click on your app’s target -> check the Supports multiple windows checkbox under deployment info.

2/ Right-click on the info.plist and choose Open as -> Source Code and copy-paste the following code snippet into it.

<key>UIApplicationSceneManifest</key>
<dict>
    <key>UIApplicationSupportsMultipleScenes</key>
    <true/>
    <key>UISceneConfigurations</key>
    <dict>
        <key>UIWindowSceneSessionRoleApplication</key>
        <array>
            <dict>
                <key>UISceneConfigurationName</key>
                <string>Default Configuration</string>
                <key>UISceneDelegateClassName</key>
                <string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
                <key>UISceneStoryboardFile</key>
                <string>Main</string>
            </dict>
        </array>
    </dict>
</dict>

Step 2:

Go to your app delegate and implement application(_:configurationForConnecting:options:)

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

Step 3:

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
  guard let windowScene = scene as? UIWindowScene else { return }
  let window = UIWindow(windowScene: windowScene)
  // Provide your apps root view controller
  window.rootViewController = rootViewController()
  self.window = window
  window.makeKeyAndVisible()
}

Things to remember:

1/ black screen appears when we run app without any view loaded into UIWindow. Try to achieve this by following steps:

Application Scene Manifest
   |
   -- Scene Configuration
       |
       -- Application Session Role
           |
           -- Storyboard Name

2/ We always need to configure our UIWindow to hold a reference to our UIWindowScene this is accomplished via the UIWindowSceneDelegate

3/ Your class that conforms to UIWindowSceneDelegate is the new AppDelegate and it holds a reference to your UIWindow

4/ [Scene] Calling -[UIApplication requestSceneSessionActivation:] requires multiwindow adoption. — this causes when you’re trying to open a new window using instance of NSUserActivity.

5/ Use same name for Configuration Name in Info.plist and func application(_:configurationForConnecting:options:) -> UISceneConfiguration in appdelegate. similarly use same name for storyboard as well.

6/ keep uniq identifier for NSUserActivity, Configuration Name and Delegate class name

Conclusion

The main reason for Apple to add UISceneDelegate to iOS 13 was to create a good entry point for multi-windowed applications. AppDelegate is responsible for handling application-level events, like app launch and the SceneDelegate is responsible for scene lifecycle events like scene creation, destruction and state restoration of a UISceneSession.

I hope, above tutorial will help to clear the concepts of scene delegate.

Reference: https://manasaprema04.medium.com/scene-delegate-vs-appdelegate-86e22dc17fcb