Introduction
Starting with iOS 13, Apple introduced the Scene lifecycle, moving window management from AppDelegate to SceneDelegate. If the Info.plist scene configuration does not correctly specify the delegate class name, or if the app delegate does not implement the scene lifecycle methods, the SceneDelegate never receives callbacks and the app shows a blank screen.
Symptoms
- App launches to a black/blank screen
SceneDelegate.scene(_:willConnectTo:options:)is never calledwindowproperty is nil in SceneDelegate- Works on iOS 12 but shows blank screen on iOS 13+
application(_:configurationForConnecting:options:)not called
Example console output:
``
Failed to find SceneDelegate for UIScene: <UIScene: 0x600001234567>
No main window found in scene delegate
Common Causes
- Info.plist
UISceneDelegateClassNamedoes not match the actual class name - Missing
UIApplicationSceneManifestin Info.plist - App delegate does not implement
application(_:configurationForConnecting:) - Scene configuration specifies wrong storyboard name
- Module name not included in class name for Swift packages
Step-by-Step Fix
- 1.Configure Info.plist correctly:
- 2.```xml
- 3.<!-- Info.plist -->
- 4.<key>UIApplicationSceneManifest</key>
- 5.<dict>
- 6.<key>UIApplicationSupportsMultipleScenes</key>
- 7.<false/>
- 8.<key>UISceneConfigurations</key>
- 9.<dict>
- 10.<key>UIWindowSceneSessionRoleApplication</key>
- 11.<array>
- 12.<dict>
- 13.<key>UISceneConfigurationName</key>
- 14.<string>Default Configuration</string>
- 15.<key>UISceneDelegateClassName</key>
- 16.<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
- 17.<key>UISceneStoryboardFile</key>
- 18.<string>Main</string>
- 19.</dict>
- 20.</array>
- 21.</dict>
- 22.</dict>
- 23.
` - 24.Implement required AppDelegate scene methods:
- 25.```swift
- 26.@main
- 27.class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { return UISceneConfiguration( name: "Default Configuration", sessionRole: connectingSceneSession.role ) }
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) { // Clean up discarded scenes } } ```
- 1.Implement SceneDelegate correctly:
- 2.```swift
- 3.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 }
window = UIWindow(windowScene: windowScene) window?.rootViewController = ViewController() window?.makeKeyAndVisible() }
func sceneDidBecomeActive(_ scene: UIScene) { // Called when scene moves to foreground }
func sceneWillResignActive(_ scene: UIScene) { // Called when scene moves to background } } ```
- 1.Opt out of scenes if you do not need them:
- 2.```xml
- 3.<!-- Remove UISceneConfiguration from Info.plist and add: -->
- 4.<key>UIApplicationSceneManifest</key>
- 5.<dict>
- 6.<key>UIApplicationSupportsMultipleScenes</key>
- 7.<false/>
- 8.</dict>
- 9.
`
```swift // AppDelegate manages window directly (pre-iOS 13 style) @main class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions options: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { window = UIWindow(frame: UIScreen.main.bounds) window?.rootViewController = ViewController() window?.makeKeyAndVisible() return true } } ```
Prevention
- Verify Info.plist scene configuration matches your SceneDelegate class name
- Use
$(PRODUCT_MODULE_NAME)for the class name to handle target naming - Test on both iOS 13+ and earlier versions if supporting older iOS
- Add assertions in AppDelegate to verify scene configuration at launch
- Document the scene lifecycle in your app architecture guide
- Consider using the
@UIApplicationDelegateAdaptorpattern for SwiftUI apps