【Xcode11】Storyboardを使わずコードだけで画面を生成する方法


目的

Xcode11でStoryboardを使わずにコードだけで画面を作ろうと思ったときにCould not find a storyboard named 'Main' in bundle NSBundleというエラーがずっと出ていてだいぶ困ったので忘れないように残しておこうと思います。
気が付いてみたらだいぶ簡単なエラーだったのですが、もし同じところで行き詰まっている人がいれば参考にしてください!笑

Storyboardを使わずに画面を生成する方法(Xcode11より前)

Xcode11より前のStoryboardを使わずにコードだけで画面を生成するときには、
1.Main.storyboardを削除。
2.プロジェクトのTARGETS > InfoにあるMain storyboard file base nameという項目を削除。

3.AppDelegate.swiftで初回の起動画面を設定。

AppDelegate.swift
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.

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

        return true
    }
}

これで初回起動時に表示される画面がViewController.swiftになると思います。

Storyboardを使わずに画面を生成する方法(Xcode11の場合)

Xcode11でプロジェクトを作成するとAppDelegate.swiftだけではなくSceneDelegate.swiftというものも一緒に生成されます。
Xcode11ではSceneDelegate.swiftでもStoryboardを使わないように設定しないといけません。
1.Main.storyboardを削除。
2.プロジェクトのTARGETS > InfoにあるMain storyboard file base nameという項目を削除。
3.プロジェクトのTARGETS > InfoにあるApplication Scene Manifest > Scene Configuration > Application Session Role > Item0(Default Configura... > Storyboard Nameを削除。(私はここでもMain.storyboardを指定していることに気がつかずにずっとエラーが出ていました!笑)

4.AppDelegate.swiftで初回の起動画面を設定。(Xcode11より前と同じ)

5.SceneDelegate.swiftで初回の起動画面を設定。

SceneDelegate.swift
class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        // 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).
        guard let scene = (scene as? UIWindowScene) else { return }
        window = UIWindow(windowScene: scene)
        window?.rootViewController = ViewController()
        window?.makeKeyAndVisible()
    }
}

これでXcode11でもStoryboardを使わずに画面を生成することができます。

iOS13以前のバージョンにも対応させる方法

iOS13以降だけでなく古いバージョンのOSにも対応させた方がいいと思うので、iOS13以前のバージョンにも対応させる方法です。
簡単にいうと、iOS13以降ではアプリ起動時にSceneDelegate.swiftが呼ばれて、iOS13以前ではAppDelegate.swiftが呼ばれるみたいなので各メソッドで@availble(iOS 13.0, *)を呼べばいいだけのようです。

AppDelegate.swift
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.

        if #available(iOS 13.0, *) {

        } else {
            //iOS13以前のときだけ呼ばれる
            window = UIWindow(frame: UIScreen.main.bounds)
            window?.rootViewController = ViewController()
            window?.makeKeyAndVisible()
        }

        return true
    }

    // MARK: UISceneSession Lifecycle

    @available(iOS 13.0, *)
    func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
        // Called when a new scene session is being created.
        // Use this method to select a configuration to create the new scene with.
        return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
    }

    @available(iOS 13.0, *)
    func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
        // Called when the user discards a scene session.
        // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
        // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
    }


}
SceneDelegate.swift
@available(iOS 13.0, *)
class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        guard let scene = (scene as? UIWindowScene) else { return }
        window = UIWindow(windowScene: scene)
        window?.rootViewController = ViewController()
        window?.makeKeyAndVisible()
    }
    func sceneDidDisconnect(_ scene: UIScene) {...}

    func sceneDidBecomeActive(_ scene: UIScene) {...}

    func sceneWillResignActive(_ scene: UIScene) {...}

    func sceneWillEnterForeground(_ scene: UIScene) {...}

    func sceneDidEnterBackground(_ scene: UIScene) {...}

}

参考にした記事

Storyboard 抜きで、コードオンリーで iOS アプリの UI を作る
Xcode11で作成したプロジェクトを古いOSに対応させる(とりあえず版)
Xcode11で作成したプロジェクトを古いOSに対応させる(完結編)
iOS13のSceneDelegate周りのアプリの起動シーケンス

さいごに

とても簡単なことですがCould not find a storyboard named 'Main' in bundle NSBundleというエラーで行き詰まってから気がつくまでだいぶ時間がかかったので同じところで困ってる人に役に立ったら幸いです!
もし間違っているところなどありましたら教えていただけると嬉しいです!