TN3187: Migrating to the UIKit scene-based life cycle
https://developer.apple.com/documentation/technotes/tn3187-migrating-to-the-uikit-scene-based-life-cycle#Provide-scene-configurations-from-your-app-delegate-for-dynamic-configuration
従来のAppDelegateでUI構築を実装しているアプリは、SceneDelegateでUI構築を実装するようにコード修正を行う必要がある。
Swiftで記述されたアプリは、Appleが公開しているマイグレーションガイドや公開されている個人デベロッパーや法人ブログを参考にしながら容易に可能だが、Objective-cで記述されたレガシーなアプリについてのマイグレーションガイドは現時点において、Meduimで公開されている個人執筆の記事しか存在しない。
App Storeで配信されているアプリには、Objective-cで記述されたレガシーなアプリが相当数あると推測され、iOS27の正式リリースより前に早めに対応することが推奨される。
本記事では、Objective-cで記述されたアプリで、画面定義にxibを使用した場合のscene-based life cycleへのマイグレーション手順を記述する。
1. info.plistの修正
UIApplicationSceneManifestキーを追加する。
<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>SceneDelegate</string>
</dict>
</array>
</dict>
Apple公式のマイグレーションガイドには、UISceneDelegateClassNameは$(PRODUCT_MODULE_NAME).SceneDelegateと記されているが、Objective-cで記述されたレガシーなアプリの場合は、
SceneDelegateと記述し、後述するようにSceneDelegateクラスを定義する。
Apple公式のマイグレーションガイドでは、UISceneStoryboardFileを指定するようになっているが、画面定義にはStoryboardではなくXibを使用しているので、UISceneStoryboardFileキーは不要である。
Apple公式のマイグレーションガイドでは、UISceneDelegateClassNameキーの記述はないが、Xcodeでinfo.plistにUIApplicationSceneManifestキーを追加すると、UISceneClassNameキーが自動で追加される。
しかしながら、Objective-c XibのUIKit scene-based life cycleへの移行時はUISceneClassNameキーは不要なので削除すること。
アプリで使用する画面の構成定義(以降「UIのセットアップ」と称する)は、移行前のAppDelegateで記述しているコードをコピーしてSceneDelegateで記述する。
info.plistにUIApplicationSceneManifestキーを定義しない場合は、AppDelegateに下記のメソッドを記述してUISceneConfigurationをコードにより記述する。
- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options {
// Create a name for the configuration
NSString *configurationName = @"Default Configuration";
// Make a new configuration
UISceneConfiguration *configuration = [[UISceneConfiguration alloc] initWithName: configurationName sessionRole: connectingSceneSession.role];
return configuration;
}
このメソッドは、info.plistにUIApplicationSceneManifestが含まれていない場合に呼ばれる。info.plistにUIApplicationSceneManifestキーがある場合はこのメソッドは呼ばれない。
iOSは、info.plistにUIApplicationSceneManifestキーが記述されているか、AppDelegateにapplication:configurationForConnectingSceneSession:options:メソッドが記述されている場合に、UISceneライフサイクルでアプリを起動する。
info.plistにUIApplicationSceneManifestキーを定義するか、configurationForConnectingSceneSessionメソッドにUISceneConfigurationをコードで記述するか、どちらか一方にすること。
info.plistにUIApplicationSceneManifestキーが定義されていれば、configurationForConnectingSceneSessionメソッドが定義されていても呼ばれないのが正しい動作だが、iOS26.4.2ではconfigurationForConnectingSceneSessionメソッドが呼ばれたり、呼ばれなかったりするバグがある。
2. ScneDelegateでUIのセットアップ記述
ScneDelegate.h
#import
@interface SceneDelegate : UIResponder
@property (nonatomic, strong) UIWindow *window;
@property (nonatomic, retain) IBOutlet UITabBarController *tabBarController;
@property (nonatomic, retain) IBOutlet UINavigationController *navigationController;
@property (nonatomic, assign) UIBackgroundTaskIdentifier backgroundTaskIdentifier;
@end
ScneDelegate.m
@implementation SceneDelegate
- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions {
// Make sure the scene is of type UIWindowScene.
// UIWindowScene is inherited from UIScene, so this check should always pass
if (![scene isKindOfClass: [UIWindowScene class]]) {
return;
}
// Make a new window scene by casting 'scene' to a UIWindowScene
UIWindowScene *windowScene = (UIWindowScene *) scene;
// Make a new window, initialized with the window scene.
self.window = [[UIWindow alloc] initWithWindowScene: windowScene];
// App Delegateのapplication:didFinishLaunchingWithOptions:で記述しているUI構築コードをここにコピーする
// Create view controller
ViewController *homeViewController = [[ViewController alloc] init];
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController: homeViewController];
// Assign root view controller
self.window.rootViewController = navigationController;
[self.window makeKeyAndVisible];
}
開発しているアプリのUIに合わせて、UITabBarControllerやUINavigationControllerの定義を記述すること。App Delegateで記述した定義と同一でよい。
self.window.rootViewControllerへのNavigationControllerの設定は、開発しているアプリのUIに合わせて修正すること。
3. ScneDelegateでアプリ起動時、バックグラウンド移行時、バックグラウンドからの復帰時のコード記述
AppDelegateで定義していたUIApplicationDelegate のapplicationDidEnterBackground、applicationWillResignActiveなどは呼ばれなくなり、対応するUISceneDelegateのメソッドに呼ばれる。
- (void)sceneDidEnterBackground:(UIScene *)scene {
// 1. バックグラウンドタスクを開始する
self.backgroundTaskIdentifier = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
// 2. 時間制限(通常30秒〜数分)までに終了しなかった場合、ここが呼ばれる
[[UIApplication sharedApplication] endBackgroundTask:self.backgroundTaskIdentifier];
self.backgroundTaskIdentifier = UIBackgroundTaskInvalid;
}];
}
- (void)sceneWillResignActive:(UIScene *)scene {
}
- (void)sceneWillEnterForeground:(UIScene *)scene {
}
- (void)sceneDidBecomeActive:(UIScene *)scene {
}
AppDelegateで定義していたアプリ起動時、バックグラウンド移行時、バックグラウンドからの復帰時のコードは、これらのメソッドに記述する。
4. App Delagateの修正
App Delegateのapplication:didFinishLaunchingWithOptions:で記述しているUIのセットアップコードはコメントアウトするか、削除する。
scene-based life cycleに移行しても、AppDelegateのapplication:didFinishLaunchingWithOptionsは呼ばれる。
UIのセットアップとアプリ起動時、バックグラウンド移行時、バックグラウンドからの復帰時のコードはSceneDelegateで実行し、それ以外の初期設定は従来通りAppDelegateで実行するようにする。
参考記事:
Apple Developer
https://developer.apple.com/documentation/technotes/tn3187-migrating-to-the-uikit-scene-based-life-cycle
Objective-c Migration
https://medium.com/@teakelld/migrating-to-uikit-scene-based-life-cycle-in-objective-c-f1a2f9402e24
日本語解説ブログ
https://dev.classmethod.jp/articles/ios27-xcode27-migration-preparation-guide/
https://techblog.lycorp.co.jp/ja/20250619a
https://buildersbox.corp-sansan.com/entry/2026/03/09/100000
https://product.st.inc/entry/2025/09/29/174637
https://zenn.dev/oka_yuuji/articles/ff6b0e1bd76566

