1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

scene-based (SceneDelegate) 対応

1
Last updated at Posted at 2025-10-08

はじめに

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)
    }
}

Universal Link からの起動

こっちも 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 userActivity = connectionOptions.userActivities.first,
            userActivity.activityType == NSUserActivityTypeBrowsingWeb,
            let url = userActivity.webpageURL
        {
            handleLink(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)
    }
}

プッシュ通知からの起動

結論、プッシュ通知の処理は AppDelegate から移行する必要はないです。おそらく Push 通知のハンドリングの大半は UNUserNotificationCenterDelegate で行われているはずで、SceneDelegate設置後もこのデリゲートで引き続きハンドリングされます。

@main
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
    func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        UNUserNotificationCenter.current().delegate = self
    }

    /// アプリがフォアの時にプッシュ通知を受信
    func userNotificationCenter(
        _ center: UNUserNotificationCenter,
        willPresent notification: UNNotification
    ) async -> UNNotificationPresentationOptions {
        let userInfo = notification.request.content.userInfo
        handlePushNotification(userInfo)
        return [.banner, .badge, .sound]
    }
    
    /// プッシュ通知バナーから起動
    func userNotificationCenter(
        _ center: UNUserNotificationCenter,
        didReceive response: UNNotificationResponse
    ) async {
        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

UIApplication の connectedScenes から現在の Scene へアクセスする手段もありますが、マルチウィンドウ環境下では適切に動作しないので避けた方が良いです。

let scene = UIApplication.shared.connectedScenes.first(where: { $0.activationState == .foregroundActive }) as? UIWindowScene
scene?.window

理由は⇩で詳しく説明されていました。
https://zenn.dev/matsuji/articles/0ee306ddfd10dc

参考

https://zenn.dev/matsuji/articles/0ee306ddfd10dc
https://qiita.com/ichikawa7ss/items/8d06d1dd23950162f436#url%E3%82%B9%E3%82%AD%E3%83%BC%E3%83%A0%E3%81%AB%E3%82%88%E3%82%8B%E8%B5%B7%E5%8B%95
https://techblog.lycorp.co.jp/ja/20250619a
https://tech.studyplus.co.jp/entry/2021/07/05/100000

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?