📖

scene-based (SceneDelegate) 対応

に公開

はじめに

WWDC 2025の中で、UIScene への対応の必須要件が発表されました。期限はiOS26の次のメジャーバージョンとのことなので例年通りなら 2026/09 前後になりそうです。

とりあえず必須要件に準拠させて安心したい方のために実作業ベースで書いてますのでご了承ください。

対象者

まだ app-based (AppDelegateしか存在してないアプリプロジェクト) な方

SceneDelegate.swift を追加する

作成

以下の内容で作成します。

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の行は削除してください。
スクリーンショット 2025-09-01 16.17.12.png

2. AppDelegateで紐付け
既存のAppDelegateに以下を追加してください。

AppDelegate.swift
@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を使ってる場合は内部で勝手にやってくれるのでこの設定は不要です。

SceneDelegate.swift
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では関数が分かれるので注意してください。

from
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
        handle(url: url)
        return true
    }
}
to
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)
    }
}

こっちも URL Scheme 同様に、起動/未起動で経路が分かれます

from
@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)
    }
}
to
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 からの起動です。これもアプリの起動/未起動で経路が分かれます。

from
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void ) {
        handle(shortcutItem: shortcutItem)
        completionHandler(true)
    }
}
to
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 に据え置きで問題ありません。

from
@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
    }
}
to
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 経由で取得させる必要があります。

from
let window = UIApplication.shared.delegate?.window
let window = UIApplication.shared.keyWindow // deprecated
to
let window = view.window

参考

https://zenn.dev/matsuji/articles/0ee306ddfd10dc
https://qiita.com/ichikawa7ss/items/8d06d1dd23950162f436#urlスキームによる起動
https://techblog.lycorp.co.jp/ja/20250619a
https://tech.studyplus.co.jp/entry/2021/07/05/100000

Discussion