在同一个多平台(iOS、macOS、watchOS、tvOS)应用程序中支持不同的生命周期方法
Posted
技术标签:
【中文标题】在同一个多平台(iOS、macOS、watchOS、tvOS)应用程序中支持不同的生命周期方法【英文标题】:Supporting different Lifecycle methods in same multiplatform(iOS, macOS, watchOS, tvOS) app 【发布时间】:2020-07-13 16:50:35 【问题描述】:这个SwiftUI-Kit 是一个开源项目,作为展示所有 SwiftUI 组件的一种方式,它支持所有 Apple 平台。
该项目是在 Xcode 12 测试版中创建的,使用新的 SwiftUI App
协议来处理应用的生命周期,部署目标是 ios 14。
现在,我想在项目中添加对 iOS 13 的支持。而且我找不到在这个项目中同时拥有适用于 iOS 14 和其他平台的 App
协议并使用适用于 iOS 13 的 AppDelegate
的方法。
我尝试了 App Delegate 和 Scene Delegate 方法的不同组合。最终结果是 iOS 13 设备崩溃并出现以下错误。
dyld: Symbol not found: _$s7SwiftUI4ViewPAAE18navigationBarTitle_11displayModeQrqd___AA010NavigationE4ItemV0f7DisplayH0OtSyRd__lFQOMQ
Referenced from: /private/var/containers/Bundle/Application/0813D699-9718-4106-BBC6/SwiftUI Kit iOS.app/SwiftUI Kit iOS
Expected in: /System/Library/Frameworks/SwiftUI.framework/SwiftUI
in /private/var/containers/Bundle/Application/0813D699-9718-4106-BBC6/SwiftUI Kit iOS.app/SwiftUI Kit iOS
dyld: launch, loading dependent libraries
DYLD_LIBRARY_PATH=/usr/lib/system/introspection
DYLD_INSERT_LIBRARIES=/Developer/usr/lib/libBacktraceRecording.dylib:/Developer/usr/lib/libMainThreadChecker.dylib:/Developer/Library/PrivateFrameworks/DTDDISupport.framework/libViewDebuggerSupport.dylib
这里是代码。您可以在 this branch 找到 iOS 13 的完整项目代码。
import UIKit
import SwiftUI
#if os(iOS)
class AppDelegate: UIResponder, UIApplicationDelegate ...
class SceneDelegate: UIResponder, UIWindowSceneDelegate
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions)
// ...
let contentView = ContentView()
// Use a UIHostingController as window root view controller.
if let windowScene = scene as? UIWindowScene
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: contentView)
self.window = window
window.makeKeyAndVisible()
func sceneDidDisconnect(_ scene: UIScene) ...
func sceneDidBecomeActive(_ scene: UIScene) ...
func sceneWillResignActive(_ scene: UIScene) ...
func sceneWillEnterForeground(_ scene: UIScene) ...
func sceneDidEnterBackground(_ scene: UIScene) ...
@main
struct MainApp
static func main()
if #available(iOS 14.0, *)
SwiftUI_Kit_iOS_App.main()
else
UIApplicationMain(
CommandLine.argc,
CommandLine.unsafeArgv,
nil,
NSStringFromClass(AppDelegate.self)
)
@available(iOS 14.0, *)
struct SwiftUI_Kit_iOS_App: App
var body: some Scene
WindowGroup
ContentView()
#else
@main
struct SwiftUI_KitApp: App
var body: some Scene
WindowGroup
#if os(macOS)
ContentView().frame(minWidth: 100, idealWidth: 300, maxWidth: .infinity, minHeight: 100, idealHeight: 200, maxHeight: .infinity)
#else
ContentView()
#endif
#endif
我查了this question,但答案需要 iOS 14 作为目标。我想让它以 iOS 13 为目标。
【问题讨论】:
iOS14之前不能使用SwiftUI Life-cycle,iOS13之前不能使用SwiftUI。 @Asperi 我不是想在 iOS 14 之前使用 SwiftUI 生命周期,我只是想知道如何在同一个项目中使用这两者,支持不同的目标。 嗯,我刚刚看到一篇关于这个的文章! swiftui-lab.com/backward-compatibility @cbjeukendrup 感谢您的链接。它适用于 iOS 14。但我在 iOS 13 中仍然遇到同样的崩溃。 我相信你不能有两个@main
属性,也许这就是重点。但是你的情况有点复杂,因为你想支持多个平台。我会仔细看看,让你知道!
【参考方案1】:
好的,我终于找到了解决办法!
不,它不漂亮,不,它不好,但它非常正确,而且我可以检查它是否有效。所以,这里是:
import SwiftUI
@main
struct MainApp
static func main()
if #available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
SwiftUIApp.main()
else
#if os(iOS) // iOS 13.0 or lower
UIApplicationMain(CommandLine.argc,
CommandLine.unsafeArgv,
nil,
NSStringFromClass(AppDelegate.self))
#else
// So we are on macOS 10.15, tvOS 13.0, watchOS 6.0 or someting lower.
// By correctly setting the deployment target in your project,
// you won't need to do someting here, as this situation will
// never occur.
print("This app doesn't run (yet) on this OS, so Bye")
return
#endif
@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
struct SwiftUIApp: App
var body: some Scene
return WindowGroup
#if os(macOS)
ContentView().frame(minWidth: 100, idealWidth: 300, maxWidth: .infinity, minHeight: 100, idealHeight: 200, maxHeight: .infinity)
#else
ContentView()
#endif
struct ContentView: View
var body: some View
Text("Hello world!")
#if os(iOS)
import UIKit
// @UIApplicationMain <- remove that!
class AppDelegate: UIResponder, UIApplicationDelegate
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool
// Override point for customization after application launch.
return true
// MARK: UISceneSession Lifecycle
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>)
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
class SceneDelegate: UIResponder, UIWindowSceneDelegate
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions)
let contentView = ContentView()
if let windowScene = scene as? UIWindowScene
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: contentView)
self.window = window
window.makeKeyAndVisible()
func sceneDidDisconnect(_ scene: UIScene) /*...*/
func sceneDidBecomeActive(_ scene: UIScene) /*...*/
func sceneWillResignActive(_ scene: UIScene) /*...*/
func sceneWillEnterForeground(_ scene: UIScene) /*...*/
func sceneDidEnterBackground(_ scene: UIScene) /*...*/
#endif
相当多的代码,对吧?
但是,(就我而言)这还不是全部。我们还需要深入研究 iOS 目标的 Info.plist 文件。
找到名为Application Scene Manifest
或UIApplicationSceneManifest
的键并将其展开(通过单击灰色三角形)
添加以下内容,使其如下图所示:
确保在“默认配置”中填写的内容与这行代码完全相同:
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
此外,“SceneDelegate”应该是 SceneDelegate
类的名称,“LaunchScreen”应该是您的启动屏幕故事板的名称(有时它写有空格,有时没有,所以要小心!)。
如果您使用我提供的代码,并且不重命名这些内容之一,这可能不会成为问题。
最后,从设备中删除该应用。这将确保在重新运行和安装时复制新的 Info.plist。 (仅在 Info.plist 中进行更改时需要)
【讨论】:
感谢您的详细回答。但是,我在您调用UIApplicationMain(CommandLine.argc, ...)
的线路上收到了 EXC_BAD_ACCESS
错误。
哦,这不是一个好兆头......而且我没有立即找到解决方案。您可以尝试按 Cmd+Shift+K 来“清理”项目,然后再次运行。有时这会有所帮助。
我已经多次清理项目。重新启动 Xcode、Mac 和 Phone。没有任何效果。
我会尽我所能!
两个建议: 1. 看来你并没有真正使用 Main.storyboard,所以也许删除它是个好主意。确保Project -> SwiftUI Kit iOS -> Deployment Info
中的“主界面”字段为空。 2. iOS 的 Info.plist 文件中有错字:LaunchScren
而不是LaunchScreen
。也许其中一件事情有帮助?以上是关于在同一个多平台(iOS、macOS、watchOS、tvOS)应用程序中支持不同的生命周期方法的主要内容,如果未能解决你的问题,请参考以下文章
苹果同步发布 macOS & watchOS 最新系统,增加不少新功能