iOS AppDelegate and Programmatic Views with Swift

The negative impact of Xcode storyboards on the development of iOS apps is well documented across the Web. Git merge nightmares usually tops the list of problems for programmers working in teams and is a deal breaker.

My primary gripe with storyboards is that they make the good practice of initializer dependency injection impossible resulting in many optionally typed properties for dependencies injected after object initialization. The code ends up being very not Swift-y. For a deeper explanation of the storyboard versus dependency injection problem see To storyboard or not to storyboard? by Jakub Turek.

This article is not really about why people dislike working with storyboards and how they make life hard. You are probably here because you already encountered storyboard problems and have your own set of reasons for wanting to create an iOS app without storyboards.

This article is about how to start an app without a storyboard in the most Swift-y way possible…

There are plenty of written and video tutorials available online showing how to bootstrap an iOS app without using storyboards. Most of the examples have AppDelegate code that looks like the following.

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

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

        window = UIWindow(frame: UIScreen.main.bounds)
        window?.rootViewController = FirstController()
        window?.makeKeyAndVisible()

        return true
    }

}

The code above works just fine. As an exercise, how can we improve on this code? I suggest the following.

import UIKit

@UIApplicationMain
final class AppDelegate: UIResponder, UIApplicationDelegate {

    private let win = UIWindow(frame: UIScreen.main.bounds)

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

        win.rootViewController = FirstController()
        win.makeKeyAndVisible()

        return true
    }

}

The property window in the first example is an optional part of the UIApplicationDelegate protocol and exists specifically for working with storyboards. It says so in the documentation. When programming without storyboards, it is still possible to use a property called window to retain the reference to the UIWindow object and avoid undesired garbage collection. Unfortunately, the protocol requires that the window property be the UIWindow? optional type. Whenever optional types can be avoided, it is definitely best to avoid them. In this case, we must choose a different variable name to avoid a conflict with the UIApplicationDelegate protocol. I’ve chosen win as the name and most importantly the property is not of optional type.

The win property is now immutable thanks to the use of let. Immutable properties definitely beat mutable properties.

The win property is now private. Private properties are definitely better than public properties.

Because the win property is the non-optional type UIWindow, there is no need to check for nil when setting the root view controller and when making the window key and visible. Every time a ? or ! can be removed from Swift code, it is an improvement.

The AppDelegate class will not be extended in our app. Marking the class as final emphasizes this. In Kotlin, final is the default if a class is not otherwise marked. It seems Kotlin was right in choosing final as the default.

In my own app, I’d actually go a few more steps and write the following with all types, self, and semicolons included. I have found that including these details makes it much easier to work with the code over time. It is easier to read the code and the compiler can catch more issues for me as I add features and refactor.

import UIKit;

@UIApplicationMain
final class AppDelegate: UIResponder, UIApplicationDelegate {

    private let win: UIWindow = UIWindow(frame: UIScreen.main.bounds);

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

        self.win.rootViewController = FirstController();
        self.win.makeKeyAndVisible();

        return true;
    }

}

Comments

Have something to write? Comment on this article.