The default Xcode iOS template create a main storyboard for the app. Here we will look into initializing an app programmatically in Swift without using storyboards. For that first we need to create a main.swift
file with code as shown in the snippet. We will also add an option for running unit tests. Since unit tests do not require UI, we will bootstrap the app without the UI code for faster test runs using an AppDelegateMock
class.
// main.swift
import Foundation
import UIKit
let isRunningTests = NSClassFromString("XCTestCase") != nil
let appDelegateClass = isRunningTests ? NSStringFromClass(AppDelegateMock.self) : NSStringFromClass(AppDelegate.self)
UIApplicationMain(CommandLine.argc, CommandLine.unsafeArgv, nil, appDelegateClass)
Here we instantiate the right app delegate class and pass it to the UIApplicationMain
as an argument. Remove the storyboard given in Main Interface
option under app target's General
section. The AppDelegate.swift
snippet is as follows.
import UIKit
class AppDelegate: UIResponder, UIApplicationDelegate {
private var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
initUI()
}
func initUI() {
window = UI.mainWindow()
UI.initMainWindow(window!, navVCRoot: MainViewController.self)
}
}
class MainViewController: UIViewController {
}
class UI {
/// Returns a main window for the screen.
static func mainWindow() -> UIWindow {
return UIWindow(frame: UIScreen.main.bounds)
}
/// Initialize main window with a navigation view controller.
static func initMainWindow(_ window: UIWindow, navVCRoot: UIViewController.Type) {
let vc = navVCRoot.init()
window.rootViewController = UI.navigationController(with: vc)
window.makeKeyAndVisible()
}
/// Initialize a navigation controller with a root view controller.
static func navigationController(with rootVC: UIViewController) -> UINavigationController {
return UINavigationController(rootViewController: rootVC)
}
}
In AppDelegate
class we remove the @UIApplicationMain
annotation and in the didFinishLaunchingWithOptions
delegate method, we initializes the UI. Here, the app uses NavigationViewController
as the root view controller. So we will create a UIWindow
and then sets the rootViewController
to a navigation view controller. We then create a navigation view controller with a view controller MainViewController
as its root. UI related code can be organized into a UI
class.
For the mock app delegate class, create a AppDelegateMock.swift
under the Tests
folder with target membership to both the main app target and the unit test target.
import Foundation
@testable import AppTargetName
class AppDelegateMock: NSObject {
private let log = Logger()
override init() {
super.init()
}
}
With this, the app now launches without a storyboard and the unit tests runs quicker as we do not initialize any UI view controllers, all done programmatically.