scene-based (SceneDelegate) 対応
はじめに
WWDC 2025の中で、UIScene への対応の必須要件が発表されました。期限はiOS26の次のメジャーバージョンとのことなので例年通りなら 2026/09 前後になりそうです。
とりあえず必須要件に準拠させて安心したい方のために実作業ベースで書いてますのでご了承ください。
対象者
まだ app-based (AppDelegateしか存在してないアプリプロジェクト) な方
SceneDelegate.swift
を追加する
作成
以下の内容で作成します。
import UIKit
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {}
func sceneDidDisconnect(_ scene: UIScene) {}
func sceneDidBecomeActive(_ scene: UIScene) {}
func sceneWillResignActive(_ scene: UIScene) {}
func sceneWillEnterForeground(_ scene: UIScene) {}
func sceneDidEnterBackground(_ scene: UIScene) {}
}
紐付け
作っただけでは動かないので紐付けます。方法は以下の2種類あってどちらか一方でOKです。
1. Info.plist
で紐付け
⇩の通り追加することで紐付けできます。StoryBoard未使用の場合は Storyboard Name
の行は削除してください。
2. AppDelegate
で紐付け
既存のAppDelegateに以下を追加してください。
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
}
いずれかの設定が終わったら func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions)
にブレーク貼って起動することで紐付けを確認できます。
window.rootViewController
を設定する
これまで AppDelegate でやってたルートViewの設定を SceneDelegate にでやります。StoryBoardを使ってる場合は内部で勝手にやってくれるのでこの設定は不要です。
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = scene as? UIWindowScene else { return }
let window = UIWindow(windowScene: windowScene)
window.rootViewController = /* your root view controller */
self.window = window
window.makeKeyAndVisible()
}
}
アプリのライフサイクルの処理をSceneDelegateへ移行
AppDelegate 側の当該関数はもう呼ばれないので、対応する SceneDelegate 関数に移行していきます。
AppDelegate | SceneDelegate |
---|---|
applicationDidBecomeActive(_:) |
sceneDidBecomeActive(_:) |
applicationWillResignActive(_:) |
sceneWillResignActive(_:) |
applicationWillEnterForeground(_:) |
sceneWillEnterForeground(_:) |
applicationDidEnterBackground(_:) |
sceneDidEnterBackground(_:) |
アプリ起動経路をSceneDelegateへ移動
AppDelegateでやってたいろんなアプリ起動経路の処理を SceneDelegate へ移動していきます。SceneDelegate.swift を追加紐付けした時点で、後述するAppDelegate側の関数たち(from)は呼ばれなくなっています。
Custom URL Scheme からの起動
AppDelegateではアプリの起動/未起動によらず一つの関数で捌けましたが、SceneDelegateでは関数が分かれるので注意してください。
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
handle(url: url)
return true
}
}
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
/// アプリ未起動からの遷移はこっち
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
if let url = connectionOptions.urlContexts.first?.url {
handle(url: url)
}
}
/// アプリ起動中バックグラウンドからの遷移はこっち
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
guard let url = URLContexts.first?.url else { return }
handle(url: url)
}
}
Universal Link からの起動
こっちも URL Scheme 同様に、起動/未起動で経路が分かれます
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
guard let url = userActivity.webpageURL else { return false }
handle(url: url)
}
}
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
/// アプリ未起動からの遷移はこっち
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
if let url = connectionOptions.urlContexts.first?.url {
handle(url: url)
}
}
/// アプリ起動中バックグラウンドからの遷移はこっち
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
guard let url = userActivity.webpageURL else { return }
handle(url: url)
}
}
Quick Action からの起動
アプリアイコン長押しで出てくる Quick Action からの起動です。これもアプリの起動/未起動で経路が分かれます。
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void ) {
handle(shortcutItem: shortcutItem)
completionHandler(true)
}
}
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
/// アプリ未起動からの遷移はこっち
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
if let shortcutItem = connectionOptions.shortcutItem {
handle(shortcutItem: shortcutItem)
}
}
/// アプリ起動中バックグラウンドからの遷移はこっち
func windowScene(_ windowScene: UIWindowScene, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) {
handle(shortcutItem: shortcutItem)
completionHandler(true)
}
}
プッシュ通知からの起動
移行が必要なのは「アプリ未起動時」の処理のみです。その他アプリ起動中の起動経路やデバイストークン関連、UNUserNotificationCenterDelegate メソッドなどは AppDelegate に据え置きで問題ありません。
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// アプリ未起動時にプッシュ通知から起動
if let userInfo = launchOptions?[UIApplication.LaunchOptionsKey.remoteNotification] as? [AnyHashable: Any] {
handlePushNotification(userInfo)
}
return true
}
}
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// アプリ未起動時にプッシュ通知から起動
if let response = connectionOptions.notificationResponse {
let userInfo = response.notification.request.content.userInfo
handlePushNotification(userInfo)
}
}
}
window
の取得方法を変更する
UIApplication.shared.delegate?.window
のように、AppDelegate のシングルトン から window を取得していた場合は、scene-based ではもう機能しないので UIView
経由で取得させる必要があります。
let window = UIApplication.shared.delegate?.window
let window = UIApplication.shared.keyWindow // deprecated
let window = view.window
参考
Discussion