DEV Community

Maarek
Maarek

Posted on

Add a Scene Delegate to your current project

With iOS 13's introduced UIWindowScene and multiple window support for iPad OS, you might be looking to add Scene Delegate to your existing app (if you are willing to add support of multi window support, for example 😉).

Before iOS 13, the main entry point for your app was the AppDelegate, and it was in charge of many logic and state handling. Now the work of the AppDelegate has been split, between the AppDelegate and the SceneDelegate.

The AppDelegate being only responsible for the initial app setup, the SceneDelegate will handle and manage the way your app is shown.
As an app could have multiple instances, a SceneDelegate will be called every time an instance of your app is created.

To add a scene delegate, first, create a new Swift file that you’ll call "SceneDelegate" containing a subclass of UIResponder, just like the AppDelegate, and that conforms to UIWindowSceneDelegate. As your app might supports other versions than iOS 13, make this class only available for iOS 13.

This is what you should have :

//
//  SceneDelegate.swift
//
import UIKit

@available(iOS 13.0, *)
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    // ...
}
Enter fullscreen mode Exit fullscreen mode

To this class, add a UIWindow property. That will be your main window, you will need it to present your View Controllers.

var window: UIWindow?
Enter fullscreen mode Exit fullscreen mode

Finally, you need to implement the SceneWillConnectToSession method in your class.
This method is the way that notifies us about the addition of a scene to the app, a scene could be seen as a window.

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

    }
Enter fullscreen mode Exit fullscreen mode

If you are working a project that is storyboard based,

you could leave this method empty.
That means that you have a UIMainStoryboardFile specified in your info.plist with a ViewController set as initial (with an arrow on it's side).

If you are not working with a main storyboard,

you should have your view hierarchy set in you AppDelegate's applicationDidFinishLaunchingWithOptions method. Then you want to add the same code in your SceneWillConnectToSession function, and instead of working with a UIWindow, it's a UIWindowScene that will host your root.
You will also want to wrap your AppDelegates code into an #available check to make sur it wont be executed if your SceneDelegate does it.

So this is what you should have in your AppDelegate :
(ViewController being your app's "Home"/"Main")

    // AppDelegate

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        if #available(iOS 13.0, *) { } else {
            self.window = UIWindow(frame: UIScreen.main.bounds)
            let mainViewController = ViewController()
            let mainNavigationController = UINavigationController(rootViewController: mainViewController)
            self.window!.rootViewController = mainNavigationController
            self.window!.makeKeyAndVisible()
        }
        return true
    }
Enter fullscreen mode Exit fullscreen mode

And in your SceneDelegate :

    // SceneDelegate

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        guard let _ = (scene as? UIWindowScene) else { return }
        if let windowScene = scene as? UIWindowScene {
            self.window = UIWindow(windowScene: windowScene)
            let mainNavigationController = UINavigationController(rootViewController: viewController)
            self.window!.rootViewController = mainNavigationController
            self.window!.makeKeyAndVisible()
        }
    }
Enter fullscreen mode Exit fullscreen mode

Finally, to make your app to use this Scene Delegate, you need to configure it as your main scene delegate. Just go in your info.plist file and add these lines :

<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>

                    <!-- ONLY IF YOU HAVE A MAIN STORYBOARD -->
                    <key>Storyboard Name</key>
                    <string>Main</string>
                </dict>
            </array>
        </dict>
    </dict>
Enter fullscreen mode Exit fullscreen mode

We make a SceneManifest in which we declare our SceneDelegate as being the default configuration for a scene.

⚠️ If you have a Main storyboard don't forget to specify it in the Storyboard Name field. If you don't, just remove the lines.

And there you are!
Now, if you execute your app on iOS/iPad OS 13, it will use the SceneDelegate, for iOS 12 and under, it will use your good old AppDelegate!

Hope you enjoyed this quick post.
If you need any help, don't hesitate to ping me on Twitter, or just leave a comment below! 😇

Happy coding!

Top comments (13)

Collapse
 
dkweb profile image
DKWeb

Hi Maarek, thanks for this great tutorial. But i have a question. I have a storyboard (uikit) App, and i want to use coredata in swiftui views. For this i need a scenedelegate. Now i have used your tutorial and leave the scene method empty. Do I have to add the manifest anyway? I added my main storyboard but only got a black screen. Even after I emptied the build. Can you help me? Thank you so much!

Collapse
 
kevinmaarek profile image
Maarek

The manifest si here to declare the scene delegate you set up (even if it contains empty methods) as being your scene delegate, the one the app will use.

If you're using storyboards, you don't to fill up the scene delegate methods. Just set the storyboard name in the plist.
So yes, you need to add a manifest in your plist file.
You info.plist should look like this (assuming the name of your storyboard is "Main") :
info.plist

You shouldn't have black screen if you have your storyboard set up and declared in these (both) plist variables.

Collapse
 
dkweb profile image
DKWeb

Hi Maarek,

Thank you for your quick response. I have the plist identical to yours. In the SceneDelegate I have the func scene empty. Still, I only get one black screen. What am I doing wrong?

Thread Thread
 
kevinmaarek profile image
Maarek

Does your storyboard has an initial view controller set (with an arrow on the left) ?

Thread Thread
 
dkweb profile image
DKWeb • Edited

Hi Maarek,

Yes I have activated an initial view controller. Do not understand why the screen stays black. As soon as I remove the manifest, the app goes back. Do you have any idea?

thepracticaldev.s3.amazonaws.com/i...
thepracticaldev.s3.amazonaws.com/i...
thepracticaldev.s3.amazonaws.com/i...

Thread Thread
 
dkweb profile image
DKWeb

I Hi Maarek,

I am now a little closer to the goal. I had to paste this code into the scene delegate:

         guard let winScene = (scene as? UIWindowScene) else {return}

         // Create the root view controller as needed
         let vc = ViewController ()
         let nc = UINavigationController (rootViewController: vc)

         // Create the window. Be sure to use this initializer and not the frame one.
         let win = UIWindow (windowScene: winScene)
         win.rootViewController = nc
         win.makeKeyAndVisible ()
         window = win

Now the ViewController is displayed, but everything is crashed. TableView (found nil), present other view controller by button crash. What is going on here?

Thread Thread
 
jacklink01 profile image
Jack Klink

Hey! Have you had any resolution to this? I am also experiencing this issue of a black screen while using a Main storyboard.

Thread Thread
 
xorforce profile image
Bhagat Singh • Edited

Hi. I guess I figured it out. The

<key> Storyboard Name </key>

is wrong. It should be

<key> UISceneStoryboardFile </key>

. It made it work for me.

Thread Thread
 
leandrooodesousa profile image
Leandro de Sousa

It does not work for me! somebody help me, please.

Collapse
 
gerkov77 profile image
gerkov77

Great tut, thanks!

Collapse
 
rukmanary profile image
Ryandhika Rukmana • Edited

Hi Maarek, can you show me how it can be done in Objective-C? I'm working on React Native Project. Thank you

Collapse
 
kevinmaarek profile image
Maarek

Hi,
SceneDelegate is just an implementation of a UIWindowSceneDelegate and UIResponder.
I know nothing about React Native, but as long as you have an App target and an info.plist file, you must be able to create an Objective-C class called SceneDelegate conforming to these and declare it in your info.plist.

Collapse
 
andrey_torlopov_769565d20 profile image
Andrey Torlopov

And what about iOS 15? How I can get current Scene or Window in xCode 13 beta and iOS 15?