SwiftUI 应用程序生命周期 iOS14应该把应用程序代码放在哪里?

既然从 SwiftUI 中删除了 AppDelegateSceneDelegate,那么我应该把以前在 SceneDelegateAppDelegate中使用的代码放在哪里呢?

所以我现在在我的 AppDelegate里有这段代码:

我现在应该把这个代码放在哪里?

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
    

FirebaseConfiguration.shared.setLoggerLevel(.min)
FirebaseApp.configure()
return true
}
39643 次浏览

Note the method below will stop cross platform support so should only be used if you are planning on building for iOS only.

It should also be noted that this doesn’t use the SwiftUI lifecycle method, instead it allows you to return to the UIKit lifecycle method.

You can still have an AppDelegate and a SceneDelegate when you create a SwiftUI app in Xcode 12-beta.

You just need to make sure that you have chosen the correct option for the Life Cycle when you create your app.

enter image description here

Make sure you choose UIKit App Delegate for the Life Cycle and you will get an AppDelegate and a SceneDelegate

Here is a solution for SwiftUI life-cycle. Tested with Xcode 12b / iOS 14

import SwiftUI
import UIKit


// no changes in your AppDelegate class
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
print(">> your code here !!")
return true
}
}


@main
struct Testing_SwiftUI2App: App {


// inject into SwiftUI life-cycle via adaptor !!!
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate


var body: some Scene {
WindowGroup {
ContentView()
}
}
}

Overriding the initializer in your App also works:

import SwiftUI
import Firebase


@main
struct BookSpineApp: App {
  

init() {
FirebaseApp.configure()
}
  

var body: some Scene {
WindowGroup {
BooksListView()
}
}
}

Find a more detailed write-up here:

You can also use the new ScenePhase for certain code that the AppDelegate and SceneDelegate had. Like going to the background or becoming active. From

struct PodcastScene: Scene {
@Environment(\.scenePhase) private var phase


var body: some Scene {
WindowGroup {
TabView {
LibraryView()
DiscoverView()
SearchView()
}
}
.onChange(of: phase) { newPhase in
switch newPhase {
case .active:
// App became active
case .inactive:
// App became inactive
case .background:
// App is running in the background
@unknown default:
// Fallback for future cases
}
}
}
}

Example credit: https://wwdcbysundell.com/2020/building-entire-apps-with-swiftui/

You should not put that kind of codes in the app delegate at all or you will end up facing the Massive App Delegate. Instead, you should consider refactoring your code to more meaningful pieces and then put the right part in the right place. For this case, the only thing you need is to be sure that the code is executing those functions once the app is ready and only once. So the init method could be great:

@main
struct MyApp: App {
init() {
setupFirebase()
}


var body: some Scene {
WindowGroup {
ContentView()
}
}
}


private extension MyApp {
func setupFirebase() {
FirebaseConfiguration.shared.setLoggerLevel(.min)
FirebaseApp.configure()
}
}

AppDelegate ?

You can have your own custom class and assign it as the delegate. But note that it will not work for events that happen before assignment. For example:

class CustomDelegate: NSObject, UIApplicationDelegate {
static let Shared = CustomDelegate()
}

And later:

UIApplication.shared.delegate = CustomDelegate.Shared

Observing For Notifications

Most of AppDelegate methods are actually observing on notifications that you can observe manually instead of defining a new class. For example:

NotificationCenter.default.addObserver(
self,
selector: #selector(<#T##@objc method#>),
name: UIApplication.didBecomeActiveNotification,
object: nil
)

Native AppDelegate Wrapper

You can directly inject app delegate into the @main struct:

@UIApplicationDelegateAdaptor(CustomDelegate.self) var appDelegate

Note: Using AppDelegate

Remember that adding AppDelegate means that you are killing default multiplatform support and you have to check for platform manually.

I see a lot of solutions where init gets used as didFinishLaunching. However, didFinishLaunching gets called AFTER init of the App struct.

Solution 1

Use the init of the View that is created in the App struct. When the body of the App struct gets called, didFinishLaunching just happened.

@main
struct MyApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate


@ViewBuilder
var body: some Scene {
WindowGroup {
MainView(appDelegate: appDelegate)
}
}
}


struct MainView: View {
  

init(appDelegate: AppDelegate) {
// at this point `didFinishLaunching` is completed
setup()
}
}

Solution 2

We can create a block to notify us when didFinishLaunching gets called. This allows to keep more code in SwiftUI world (rather than in AppDelegate).

class AppDelegate: NSObject, UIApplicationDelegate {


var didFinishLaunching: ((AppDelegate) -> Void)?


func application(
_ application: UIApplication,
didFinishLaunchingWithOptions
launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil
) -> Bool {
didFinishLaunching?(self)
return true
}
}


@main
struct MyApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate


@ObservedObject private var applicationModel = ApplicationModel()


// `init` gets called BEFORE `didFinishLaunchingWithOptions`
init() {


// Subscribe to get a `didFinishLaunching` call
appDelegate.didFinishLaunching = { [weak applicationObject] appDelegate in


// Setup any application code...
applicationModel?.setup()
}
}


var body: some Scene {
return WindowGroup {
if applicationObject.isUserLoggedIn {
LoggedInView()
} else {
LoggedOutView()
}
}
}
}

I would also advise in using the main App's init method for this one, as it seems safe to use (any objections?).

What I usually do, that might be useful to share, is to have a couple of utility types, combined with the Builder pattern.

/// An abstraction for a predefined set of functionality,
/// aimed to be ran once, at app startup.
protocol StartupProcess {
func run()
}


/// A convenience type used for running StartupProcesses.
/// Uses the Builder pattern for some coding eye candy.
final class StartupProcessService {
init() { }


/// Executes the passed-in StartupProcess by running it's "run()" method.
/// - Parameter process: A StartupProcess instance, to be initiated.
/// - Returns: Returns "self", as a means to chain invocations of StartupProcess instances.
@discardableResult
func execute(process: any StartupProcess) -> StartupProcessService {
process.run()
return self
}
}


and then we have some processes

struct CrashlyticsProcess: StartupProcess {
func run() {
// Do stuff, like SDK initialization, etc.
}
}


struct FirebaseProcess: StartupProcess {
func run() {
// Do stuff, like SDK initialization, etc.
}
}


struct AppearanceCustomizationProcess: StartupProcess {
func run() {
// Do stuff, like SDK initialization, etc.
}
}

and finally, running them

@main
struct TheApp: App {
init() {
initiateStartupProcesses()
}


var body: some Scene {
WindowGroup {
ContentView()
}
}
}


private extension TheApp {
func initiateStartupProcesses() {
StartupProcessService()
.execute(process: ExampleProcess())
.execute(process: FirebaseProcess())
.execute(process: AppearanceCustomizationProcess)
}
}

Seems quite nice and super clean.