
STORES ブランドアプリの iOS 版を開発している Megabits です。
UISceneDelegate は iOS 13 で追加され、 6 年が経ちました。最初は iPad でのマルチタスクを管理するためのものでした。同じアプリでも、複数ウィンドウを持つ可能性があるため、シーンで分けてそれぞれのライフサイクルを管理する需要がありました。UISceneDelegate はこの仕事をするクラスです。
しかし、UISceneDelegate への移行は強制ではないため、特に iPad をサポートする予定がなければ、対応しなくても影響はありませんでした。ただ、実行する時に、このような警告が出ます。
This process does not adopt UIScene lifecycle. This will become an assert in a future version.
この状況は今年 iOS 26 の発表とともに変わって、今後サポートしないと新しい SDK でビルドできなくなり、今はこのような警告が出るようになります。
UIScene lifecycle will soon be required. Failure to adopt will result in an assert in the future.
WWDC 25 のトークでもこのような説明がありました。
“As scenes are vital for ensuring flexibility, adopting UIScene life cycle will soon be mandatory. In the next major release following iOS 26, UIScene life cycle will be required when building with the latest SDK.” ー Make your UIKit app more flexible
STORES ブランドアプリチームは、今年七月頃にこの対応を完了しました。対応自体は複雑ではないですが、注意が必要なところも色々あります。
最初は TN3187 を一回読むと良いでしょう。
サポートを宣言する
古いプロジェクトに UISceneDelegate を追加するとき、まず Scene Base Life Cycle をサポートする宣言をしないといけません。この宣言は二つ方法あります。どちらを使っても OK です。新しいアプリを作成する時、info.plist と AppDelegate 両方に関連の設定が追加されますが、試したところどちらかがあれば大丈夫です。
info.plist に追加
info.plist で Scene Configuration の名前、それを制御する SceneDelegate を定義しました。新しいアプリだと Storyboard の定義も自動的に追加されますが、Storyboard を使わないのであれば書かなくてもかまいません。
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <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> </plist>

AppDelegate で宣言
AppDelegate で宣言することでも同じことを実現できます。管理しやすいため、私はこちらを使用しました。
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { let sceneConfiguration = UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) sceneConfiguration.delegateClass = SceneDelegate.self return sceneConfiguration }
SceneDelegate の追加とライフサイクルの移行
SceneDelegate の基本的な内容は以下になります。Storyboard を使わず自分で UIWindow を設定する場合、その処理を willConnectTo に移動します。
import UIKit class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { if let windowScene = scene as? UIWindowScene { let window = UIWindow(windowScene: windowScene) window.rootViewController = RootViewController() self.window = window window.makeKeyAndVisible() } } func sceneDidBecomeActive(_ scene: UIScene) { } func sceneWillResignActive(_ scene: UIScene) { } }
SceneDelegate を追加すると、以前 AppDelegate で書かれたライフサイクルに関わる一部の関数は、呼び出されなくなります。そのため、元々 AppDelegate で書いたコードを SceneDelegate に移動する必要があります。
| UIApplicationDelegate | UISceneDelegate |
|---|---|
| applicationDidBecomeActive(_:) | sceneDidBecomeActive(_:) |
| applicationWillResignActive(_:) | sceneWillResignActive(_:) |
| applicationDidEnterBackground(_:) | sceneDidEnterBackground(_:) |
| applicationWillEnterForeground(_:) | sceneWillEnterForeground(_:) |
ウィンドウの取得を修正する
SceneDelegate を対応する前、rootViewController を取りたい時は大体こんな感じで書きます。
if let root = UIApplication.shared.windows.first?.rootViewController { // ... }
対応した後、古い方法が使えなくなり、新しい書き方に置き換える必要があります。
if let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene, let root = scene.windows.first?.rootViewController { // ... }
iOS デバイスのみをサポートする場合はこの書き方でも大丈夫ですが、複数のシーンが同時に表示されている場合(例えば iPad のマルチタスク、もしくは外付け画面)、欲しいもの以外のものが取得される可能性があります。その場合事前に UIWindowScene を保存する工夫が必要です。状況に応じて対応しましょう。
そして、このようなコードがアプリに複数存在する場合、もれなく修正しましょう。
URL Scheme の処理を移行する
ライフサイクル以外のものに関して、URLScheme 関連の処理も SceneDelegate に移動する必要があります。
アプリが起動されてない状態で呼ばれたら、URLScheme は willConnectTo で処理されます。すでに起動されいているのであれば、performActionFor で処理されます。漏れなく追加しましょう。
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { // ... if let shortcutItem = connectionOptions.shortcutItem { handleShortcutItem(shortcutItem) } } func windowScene(_ windowScene: UIWindowScene, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) { handleShortcutItem(shortcutItem) }
終わりに
SceneDelegate を追加すること自体は複雑ではありませんが、修正が漏れやすいところもあります。特に rootViewController を参照するところはアプリのあちこちに存在する可能性があるため、細かく動作確認しましょう。