DEV Community

Chris Myers
Chris Myers

Posted on

Coordinator Setup Changes

It's been awhile since I set up a coordinator flow in a project. My previous project was well established and once the flow was set up to start the project, it was rarely modified.

For those that are unfamiliar with the Coordinator pattern, there are many blog posts regarding use and set up. These are some of my favorites to look at because I always need a refresher when setting up a new project.

So imagine my surprise when I decide to create a new sample project last week and couldn't find var window: UIWindow? anywhere in the AppDelegate. It's just not there.

The Old AppDelegate

Old AppDelegate

The New AppDelegate

New AppDelegate

As it turns out, some of the delegate methods associated with the AppDelegate have been migrated over to a new file that comes out of the box when setting up a new project, SceneDelegate when Apple released iOS 13. A wonderful overview of the differences in AppDelegate and SceneDelegate can be found here: Understanding the iOS 13 Scene Delegate

I strongly encourage reading the article, especially about changes to the info.plist, which I won't cover in this post.

The SceneDelegate

Scene Delegate

The SceneDelegate file now contains the window property.
var window: UIWindow?

Okay, no big deal, I'll set up my coordinator from here.

Still doesn't work

Well, looking at the developer documentation, the AppDelegate still conforms to UIApplicationDelegate, but the delegate methods have changed. It is still the file to use to register for Push Notifications, responding to those notifications, responding to events that target the app itself, but there is also a new area that allows you to configure the app scenes--specifically, connecting to and discarding SceneSessions.

The SceneDelegate file conforms to UISceneWindowDelegate, which itself conforms to UISceneDelegate. The window property is part of the UIWindowSceneDelegate and the main window associated with the scene.

The associated functions in the SceneDelegate are protocol methods from UISceneDelegate. They handle what the app should do if the scene enters the foreground, goes to the background and handle any other life-cycle events occurring within a scene.

But getting back to the window: UIWindow property, there is a new way to initialize this variable. In the past, initializing the window property in the AppDelegate was something like this:

self.window = UIWindow(frame: UIScreen.main.bounds)

You can still set up your window this way. UIScreen.main.bounds still is available. However, with the introduction to Scenes, Apple lets us know that there is a new, preferred way to initialize the UIWindow.

Inside of the delegate method, func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) sits this comment:

Use this method to optionally configure and attach the UIWindow window to the provided UIWindowScene scene.

If using a storyboard, the window property will automatically be initialized and attached to the scene.

This delegate does not imply the connecting scene or session are new (see application:configurationForConnectingSceneSession instead).

So instead of using UIScreen to initialize the window frame, this is what is expected:

  • Step One: safely unwrap the provided scene: UIScene in the delegate method and cast it as a UIWindowScene.
  • Step Two: Initialize the window property using the unwrapped UIWindowScene.
  • Step Three: set the windowScene property now found in a UIWindow with the unwrapped windowScene from step one.

Step three is vitally important, because if this step is missed, you'll just get a blank screen when running the app.

In code, this looks like the following:

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    guard let windowScene = (scene as? UIWindowScene) else { return }
    let uiWindow = UIWindow(frame: windowScene.coordinateSpace.bounds)
    self.window = uiWindow
    self.window?.windowScene = windowScene
}
Enter fullscreen mode Exit fullscreen mode

Thus far, I haven't mentioned anything about the actual coordinator. I wanted to separate the two, because once the window initialization is set up, the coordinator piece is pretty straight forward.

First, start with a protocol... :)

protocol Coordinator {
    func start()
}
Enter fullscreen mode Exit fullscreen mode

Next, create an application coordinator conforming to the protocol. (I'm using the Ray Wenderlich blog post I linked above as my sample AppCoordinator)

class AppCoordinator: Coordinator {
    let window: UIWindow
    let rootViewController: UINavigationController

    init(_ window: UIWindow) {
    self.window = window
    rootViewController = UINavigationController()

    let mainVC = MainViewController()
    rootViewController.pushViewController(mainVC, animated: true)
    }

    func start() {
    window.rootViewController = rootViewController
    window.makeKeyAndVisible()
    }
}
Enter fullscreen mode Exit fullscreen mode

After that, it's just a matter of modifying the previously mentioned delegate method in the SceneDelegate.

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    guard let windowScene = (scene as? UIWindowScene) else { return }
    let uiWindow = UIWindow(frame: windowScene.coordinateSpace.bounds)
    self.window = uiWindow
    self.window?.windowScene = windowScene

    let appCoordinator = AppCoordinator(uiWindow)
    self.appCoordinator = appCoordinator
    appCoordinator.start()
}
Enter fullscreen mode Exit fullscreen mode

I chose to set up my app without using storyboards. Just note, to remove references to the storyboard, the main storyboard can be found in the SceneConfiguration in the plist. Just deleting from the Main Interface in the App Target isn't enough.

New location in plist

You can absolutely use coordinators with a storyboard (See Paul Hudson's article linked above) if you so choose.

Top comments (0)