Help us understand the problem. What is going on with this article?

LaunchScreenを表示したままにしてViewControllerを動かす

やりたいこと

S12019-09-17 6.11.02.png

LaunchScreenを表示した後、画面はそのままで初期化用のStartupViewControllerを起動します。
初期化終了後にMainViewControllerを起動します。

StartupViewControllerを起動する

AppDelegateのdidFinishLaunchingWithOptions辺りでStartupViewControllerをrootViewControllerにセットします。

AppDelegate.swift
// root view controller
let viewController = StartupViewController()
window.rootViewController = viewController
window.makeKeyAndVisible()

LaunchScreenのStoryboard名をInfo.plistから取得する

ハードコードで"LaunchScreen"でもいいんですけど。:relaxed:

InfoPlistUtil.swift
final class InfoPlistUtil {
    private init() {}

    static func objectForKey<T>(_ key: String) -> T? {
        Bundle.main.object(forInfoDictionaryKey: key) as? T
    }

    static func stringForKey(_ key: String) -> String? {
        InfoPlistUtil.objectForKey(key)
    }

    static var launchStoryboardName: String? {
        InfoPlistUtil.stringForKey("UILaunchStoryboardName")
    }
}

LaunchScreenを取得する

LaunchScreenをViewControllerごとインスタンス化してそのviewを取得します。

ViewUtil.swift
final class ViewUtil {
    private init() {}

    static var launchScreen: UIView? {
        guard let name = InfoPlistUtil.launchStoryboardName else {
            return nil
        }
        let storyboard = UIStoryboard(name: name, bundle: nil)
        let viewController = storyboard.instantiateInitialViewController()
        let retainedView = viewController?.view // retain Launch Screen
        viewController?.view = nil // Cut reference from viewController to Launch Screen
        return retainedView
    }
}

肝なところなので解説もご覧ください。

StartupViewControllerにLaunchScreenをセットする

StartupViewControllerのviewにLaunchScreenをセットします。

StartupViewController.swift
override func loadView() {
    super.loadView()

    if let launchScreen = ViewUtil.launchScreen {
        view = launchScreen
    }
}

MainViewControllerを起動する

初期化終了後にMainViewControllerをインスタンス化してrootViewControllerにセットします。

このまま実行できませんが雰囲気はわかりますよね?ベタで書くの面倒だったので。:relaxed:

StartupViewController.swift
// 初期化終了後
let viewController = ViewUtil.retrieveViewController(MainViewController.self)
RootViewControllerSegue(source: self, destination: viewController)
    .perform()

解説

ダメパターン(1)

001.png
instantiateInitialViewController()を実行した直後はLaunchScreenが無名ViewControllerに割り当てられています。
この状態でLaunchScreenをStartupViewControllerに割り当てるとクラッシュします。
(1つのUIViewを複数のUIViewControllerに割り当てできないため)

ダメパターン(2)

002.png
無名ViewControllerからLaunchScreenへの参照を切ってみたらどうでしょうか?
LaunchScreenの参照数が0になってしまうので、LaunchScreenがメモリから解放されてしまいます。
(詳細はARCの仕組みを調べてみてください)

成功パターン

003.png
1. 変数retainedViewを用意してLaunchScreenを参照することで参照数が0になることを防ぎます。
2. 無名ViewControllerからLaunchScreenへの参照を切ります。
3. LaunchScreenをStartupViewControllerに割り当てます。


Xcode11 GM2 + iOS13.0 GM

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away