Поддержка разных методов жизненного цикла в одном мультиплатформенном (iOS, macOS, watchOS, tvOS) приложении - PullRequest
0 голосов
/ 13 июля 2020

Этот SwiftUI-Kit - проект с открытым исходным кодом, который действует как способ демонстрации всех компонентов SwiftUI и поддерживает все платформы Apple.

Проект был создан в бета-версии Xcode 12 , с новым протоколом SwiftUI App для обработки жизненного цикла приложения и целью развертывания iOS 14.

Теперь я хочу добавить в проект поддержку iOS 13. И я не могу найти способ иметь протокол App для iOS 14 и других платформ и использовать AppDelegate для iOS 13 в этом проекте.

Я пробовал разные комбинации App Delegate и методы делегирования сцены. Конечным результатом является cra sh в 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

Вот код. Вы можете найти полный код проекта для 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

Я просмотрел этот вопрос , но ответы требуют iOS 14 как цель. Я хочу, чтобы он работал с iOS 13 в качестве цели.

1 Ответ

0 голосов
/ 15 июля 2020

Хорошо, я наконец нашел решение!

Нет, это не красиво, и нет, это не красиво, но вполне корректно и работает, насколько я могу проверить. Итак, вот оно:

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

Довольно много кода, да?

Но (в моем случае) это еще не все. Нам также нужно погрузиться в файл Info.plist для цели iOS.

  1. Найдите ключ с именем Application Scene Manifest или UIApplicationSceneManifest и разверните его (щелкнув серый треугольник )

  2. Добавьте следующие элементы, чтобы он выглядел как на картинке ниже: Info.plist

    Убедитесь, что в «Конфигурации по умолчанию» вы указываете точно так же, как в этой строке кода:

            return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
    

    Кроме того, «SceneDelegate» должно быть именем класс SceneDelegate и LaunchScreen должны быть именем раскадровки вашего экрана запуска (иногда он пишется с пробелом, иногда без него, поэтому будьте осторожны!).

    Если вы используете предоставленный мной код, и не переименовывайте одну из этих вещей, это, вероятно, не будет проблемой.

  3. Наконец, удалите приложение с устройства. Это гарантирует, что новый Info.plist будет скопирован при повторном запуске и установке. (необходимо только при внесении изменений в Info.plist)

...