SceneDelegate対応した手順や参考にした情報
概要
WWDC25のセッション動画 Make your UIKit app more flexible にて次のメジャーアップデート(つまりiOS27)にてSceneDelegate対応が必須になるため、先行して対応しました。
「そもそも自分のアプリが対象なのかよくわからない」
という人も多いのではと思ったので自分が対応した際に参考になった記事などを参考に、情報を整理しました。
SceneDelegate対応の対象か
とりあえず
- サポートバージョンが13.0以降
- UIKitがベース
- AppDelegateのみでSceneDelegateはない
のであれば対象です。
SwiftUIがベースの場合は
- SwiftUIはデフォルトでscene-basedライフサイクルを採用している
-
WindowGroup
が自動的にUIWindowScene
に対応しているため対応不要 - ただしiPadOSの複数ウィンドウ対応では
UIApplicationSceneManifest
の追加が必要
という感じです。
app-basedやscene-basedに関しては以下の記事が大変わかりやすいかと思います。
まとめると、
- UIKitベースかつAppDelegateしかない場合 → 移行必須
- SwiftUIベース → 基本不要(ただしiPadOSの複数ウィンドウ対応では必要)
対応方法について
まずものすごく簡単に移行手順を話すと
- info.plistに
UIApplicationSceneManifest
を追加 - SceneDelegateを作成
- AppDelegateにあるWindow周りを全て移行
こんな感じです。
対応する前にインプットした方がいい情報
実装イメージがわかないという人向けに最低限見ておいた方がよさそうなものを列挙しておきます。
この動画を見るとざっくりとAppDelegateからSceneDelegateへの移行がイメージ付きます。
- WWDC19: Architecting Your App for Multiple Windows
UIKitでscene-basedに移行するテクニカルノートを読むと移行判断や手順の概要を掴むことができます。
- TN3187: Migrating to the UIKit scene-based life cycle
一旦この二つ見るだけで大体の移行に関するイメージは掴めるかと思います。
実装手順
ざっくりとした手順は上記に列挙しましたが、ここではちょっとした解説をしていきたいと思います。
UIApplicationSceneManifest
を追加
info.plistにTN3187にある通り設定すれば基本問題ないです。
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneConfigurationName</key>
<string>Default Configuration</string>
<key>UISceneDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
<key>UISceneStoryboardFile</key>
<string>Main</string>
</dict>
</array>
</dict>
</dict>
重要な部分だけ少し解説を書いておきます。
-
UIApplicationSupportsMultipleScenes
: マルチウィンドウのサポートを有効にする場合はtrue
にしてください。 -
UISceneConfigurationName
: シーン構成の一意の名前。複数でなければ基本一つでDefault Configuration
で問題ありません。 -
UISceneDelegateClassName
: SceneDelegateクラスの指定。基本的にはSceneDelegateと命名するのでサンプル通り$(PRODUCT_MODULE_NAME).SceneDelegate
で問題ないかと思います。
(別名つけている場合は適宜修正してください)
SceneDelegateを作成
SceneDelegateのサンプルコードも記載されています。
import UIKit
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(
_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions
) {
// Confirm the scene is a window scene in iOS or iPadOS.
guard let windowScene = scene as? UIWindowScene else { return }
window = UIWindow(windowScene: windowScene)
window?.rootViewController = YourRootViewController()
window?.makeKeyAndVisible()
}
}
まずはこのWindowをAppDelegateから移行します。
よってAppDelegateにあるWindowは削除して、Window周りもこちらに移行していけば良いです。
AppDelegateからの移行
移行に関しては
Migrate app life-cycle logicセクションを参考にマッピングされたAPIに移行していけばいいですが、UniversalLinksなどの実装があると他に対応する必要があるのでそれは各プロダクトのAppDelegateにあるSceneに関連するものを適時移行します。
対応するマッピングとして以下のサイトが非常にわかりやすかったので貼っておきます。
またUniversal LinksやPush通知などconnectionOptions
を扱う処理はSceneDelegateに移す必要がありますのでこの辺り参考になると思います。
WIP: 参考記事いくつか
移行のTips
移行作業を簡単にする
個人的に以下の方法で移行してサクッとできたので紹介します。
- まずAppDelegateにあるDelegate Methodを検索する
- 移行対象ならドキュメントにiOS 26 でDeprecatedとなる旨とSceneDelegateで使用するAPIが記載されているので対応する
以上です。
Universal Links経由でコールド起動をするとアプリがクラッシュする
scene(_:openURLContexts:)
で処理しているので問題ないかと思いましたが、Universal Links経由でコールド起動をするとアプリがクラッシュしました。
AppDelegateでは暗黙的に初期化後に処理されていたみたいですが、SceneDelegateでは明示的に扱う必要があるみたいです。つまりSceneDelegateではコールド起動時のDeepLinkや通知のハンドリング場所に注意が必要のようです。
以下の記事が大変参考になりましたが、おそらくscene(_:willConnectTo:options:)
はUI初期化前に呼ばれるため、画面遷移などをここで直接行うと不安定になるようです。
sceneDidBecomeActive
でDeepLink
、Universal Links
、通知レスポンスを処理することでクラッシュせずに実行することができました。
以上です。
Discussion