【问题标题】:Xcode 12 project with iOS 13 SwiftUI support: SceneDelegate functions not invoked支持 iOS 13 SwiftUI 的 Xcode 12 项目:未调用 SceneDelegate 函数
【发布时间】:2025-12-29 14:10:12
【问题描述】:

我在 Xcode 12 中创建了一个项目,其中 iOS 14 作为部署目标(更大的项目)。一段时间后,我意识到我还必须能够支持 iOS 13 设备。我的项目使用 SwiftUI。

将部署目标简单地更改为 iOS 13 显然不是很直接,因为 Xcode 12 生成的某些代码不向后兼容 iOS 13。

所以我基本上需要将我的基于 Xcode 12 的项目(当前部署目标为 iOS 14.0)转换为也支持 iOS 13

这是我为项目降级所做的准备工作:

  • 安装 Xcode 11.7,创建支持 SwiftUI 的新项目,并确保部署目标是 iOS 13.0。因此,我知道 Xcode 认为“iOS 13”方式应该是什么,即使用 AppDelegate 和 SceneDelegate。
  • 运行该应用 - 在我的 iOS 14 设备上运行良好。
  • 打开 Xcode 12,创建支持 SwiftUI 的新项目,将部署目标降低到 iOS 13.0(因为它默认设置为 14.x),构建它,然后出现一堆编译问题(如预期的那样)。

随后对 Xcode 12 项目进行了更改:

  • 注释掉<project>App.swift 文件中的所有内容。
  • 添加与上述 Xcode 11.7 生成的项目中内容相同的 AppDelegate.swift 文件。
  • 添加与上述 Xcode 11.7 生成项目中内容相同的 SceneDelegate.swift 文件。
  • 添加 LaunchScreen.storyboard 文件(因为 Xcode 警告它不存在)。
  • 运行应用程序。

应用程序运行,但ContentView 的内容未显示。只是一个黑屏(黑色可能是因为我的设备处于黑暗模式)。此外,正如您将在下面的代码中注意到的那样,我插入了调试日志,但控制台中只打印了“didFinishLaunchingWithOptions”——也就是说,似乎没有调用场景委托方法。

我(当然)用 Google 搜索了很多,用 Info.plist 尝试了各种方法,但似乎没有任何帮助。

代码:

AppDelegate.swift

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        print("didFinishLaunchingWithOptions")
        return true
    }
    
    // MARK: UISceneSession Lifecycle
    
    func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
        print("configurationForConnecting")      
    }
    
    func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
        print("didDiscardSceneSessions")
    }
}

SceneDelegate.swift

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    
    var window: UIWindow?
    
    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        print("scene willConnectTo")
        guard let windowScene = scene as? UIWindowScene else { return }
        let window = UIWindow(windowScene: windowScene)
        window.rootViewController = UIHostingController(rootView: ContentView())
        self.window = window
        window.makeKeyAndVisible()
    }
    
    func sceneDidDisconnect(_ scene: UIScene) {
        print("sceneDidDisconnect")
    }
    
    func sceneDidBecomeActive(_ scene: UIScene) {
        print("sceneDidBecomeActive")
    }
    
    func sceneWillResignActive(_ scene: UIScene) {
        print("sceneWillResignActive")
    }
    
    func sceneWillEnterForeground(_ scene: UIScene) {
        print("sceneWillEnterForeground")
    }
    
    func sceneDidEnterBackground(_ scene: UIScene) {
        print("sceneDidEnterBackground")
    }
}

ContentView.swift

struct ContentView: View {
    
    var body: some View {
        Text("Hello, world!")
            .foregroundColor(.blue)
            .padding()
            .onAppear {
                print("ContentView")
            }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Info.plist

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>CFBundleDisplayName</key>
    <string></string>
    <key>LSApplicationCategoryType</key>
    <string></string>
    <key>CFBundleDevelopmentRegion</key>
    <string>$(DEVELOPMENT_LANGUAGE)</string>
    <key>CFBundleExecutable</key>
    <string>$(EXECUTABLE_NAME)</string>
    <key>CFBundleIdentifier</key>
    <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
    <key>CFBundleInfoDictionaryVersion</key>
    <string>6.0</string>
    <key>CFBundleName</key>
    <string>$(PRODUCT_NAME)</string>
    <key>CFBundlePackageType</key>
    <string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
    <key>CFBundleShortVersionString</key>
    <string>1.0</string>
    <key>CFBundleVersion</key>
    <string>1</string>
    <key>LSRequiresIPhoneOS</key>
    <true/>
    <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>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
                </dict>
            </array>
        </dict>
    </dict>
    <key>UIApplicationSupportsIndirectInputEvents</key>
    <true/>
    <key>UILaunchScreen</key>
    <dict/>
    <key>UILaunchStoryboardName</key>
    <string>LaunchScreen</string>
    <key>UIRequiredDeviceCapabilities</key>
    <array>
        <string>armv7</string>
    </array>
    <key>UISupportedInterfaceOrientations</key>
    <array>
        <string>UIInterfaceOrientationPortrait</string>
        <string>UIInterfaceOrientationLandscapeLeft</string>
        <string>UIInterfaceOrientationLandscapeRight</string>
    </array>
    <key>UISupportedInterfaceOrientations~ipad</key>
    <array>
        <string>UIInterfaceOrientationPortrait</string>
        <string>UIInterfaceOrientationPortraitUpsideDown</string>
        <string>UIInterfaceOrientationLandscapeLeft</string>
        <string>UIInterfaceOrientationLandscapeRight</string>
    </array>
</dict>
</plist>

【问题讨论】:

  • 我看不出这有什么意义。在 Xcode 12 中创建 SwiftUI 项目时,您可以选择使用向后兼容的应用程序委托生命周期。所以就这样做吧。
  • @matt 是的,如果我要开始一个新项目,那会起作用。我的观点是我有一个现有的 Xcode 12 项目,它是使用“SwiftUI 方式”创建的,因此我需要一种方法来配置现有项目以使用“非 SwiftUI 方式”。我只是尝试根据您的建议创建一个新项目,并试图从中找出不同之处。唯一的问题是它使用@main 而不是@UIApplicationMain,但就我所见,仅此而已。

标签: ios swiftui ios13 ios14 xcode12


【解决方案1】:

我猜你忘记将场景委托文件添加到应用程序目标。构建和运行时,查看控制台,看看是否有关于找不到场景委托的抱怨。这可以解释这种现象。

【讨论】:

    【解决方案2】:

    如果您在实施SceneDelegate 之前已经使用 SwiftApp 构建了应用程序,只需卸载并重建它。然后,你就可以解决这个问题了。

    persistentIdentifier 将由系统在第一次启动应用程序时生成用于创建新的Scene。之后,每个应用程序打开的标识符都是相同的。所以应用不需要创建新的场景。

    这就是为什么 configurationForConnecting 没有被调用但没有设置 rootview 的原因,所以你的应用程序会显示黑色窗口。

    以下 WWDC19 会议视频(大约 31:40)介绍了标识符。
    https://developer.apple.com/videos/play/wwdc2019/212/?time=1900

    希望对您的问题有所帮助。
    谢谢。

    【讨论】: