当应用程序放入后台时,SwiftUI 应用程序返回到第一个屏幕

Dan*_*avo 5 ios swiftui

我在 SwiftUI 中构建了一个应用程序,但遇到了一个恼人的问题。

当应用程序出现时,应用程序会显示自定义启动屏幕,同时应用程序的数据会异步加载。完成后,应用程序的主视图将呈现给用户。一旦用户到达这一点,一旦他们将应用程序拖回到后台,导航堆栈就会无缘无故地弹回第一个启动屏幕。

以下是相关代码片段:

MyApp.swift

@main
struct Jam500App: App {
    
    @Environment(\.scenePhase) var lifecycle

    init() {
        //initial setup goes here
    }
    
    var body: some Scene {
        WindowGroup {
                LaunchView()
        }
    }
    
}
Run Code Online (Sandbox Code Playgroud)

LaunchView.swift

struct LaunchView: View {
    
    @ObservedObject var control: LaunchViewControl = LaunchViewControl()
    
    var body: some View {
        NavigationView(content: {
            ZStack {
                //some view hierarchy                 
                NavigationLink(destination: RootView(),
                               isActive: $control.isReady,
                               label: {EmptyView()})

            }
            .onAppear(perform: {
                control.loadData()
            })
            .navigationBarTitle("")
            .navigationBarHidden(true)
            .statusBar(hidden: true)
        }).navigationViewStyle(StackNavigationViewStyle())
    }
    
}
Run Code Online (Sandbox Code Playgroud)

LaunchViewControl.swift

final class LaunchViewControl: ObservableObject {
    
    @Published var isReady: Bool = false
    private var executing: Bool = false
    private let dataService = DataService.shared
    
    func loadData() {
        //runs all initial app setup needed before the app starts (asynchronously)
        guard !executing else {
            return
        }
        executing = true
        self.setupCoreData()
    }
    
    private func setupCoreData() {
        //Initialises CoreData with CloudKit (asynchronously)
        CloudCoreDataService.shared.setup { [weak self] in
            guard let self = self else { return }
            self.checkRequiresInitialData()
        }
    }
    
    private func checkRequiresInitialData() {
        self.dataService.requiresInitialDataSetup(onCompletion: { [weak self] (isRequired) in
            guard let self = self else { return }
            if isRequired {
                self.setupInitialData()
            } else {
                self.isReady = true
            }
        })
    }
    
    private func setupInitialData() {
        //Generates initial database if needed
        self.dataService.setupInitialData { [weak self] (_) in
            guard let self = self else { return }
            self.isReady = true
        }
    }
    
}
Run Code Online (Sandbox Code Playgroud)

根视图.swift

struct RootView: View {
    
    @ObservedObject var control = RootViewControl()
    
    var body: some View {
        HStack(spacing: 0) {
            //some view hierarchy
        }
        .navigationBarTitle("")
        .navigationBarHidden(true)
        .statusBar(hidden: true)
    }
    
}
Run Code Online (Sandbox Code Playgroud)

还有其他人在 Xcode 12.4 中遇到过这个问题吗?

Bur*_*kaş 4

您可以应用两种解决方案(我想到的,并且不会引起很多变化):

1.注射

当您在视图中初始化“LaunchViewControl”类时:

@ObservedObject var control: LaunchViewControl = LaunchViewControl()
Run Code Online (Sandbox Code Playgroud)

当视图被丢弃和重绘时,SwiftUI 会一次又一次地初始化该控制变量,因为 ObservedObject 与 State 变量不同,并且 SwiftUI 不会跟踪它们的状态。

要尝试此操作,请取消注释“scenePhase”行并将“init”方法添加到“LaunchViewControl”,然后添加一些打印语句以了解发生了什么,或添加断点。

例如:

final class LaunchViewControl: ObservableObject {
    // ....
    init() {
        print("initialized")
    }
    // ....
}
Run Code Online (Sandbox Code Playgroud)

您将在控制台上看到多个“初始化”日志。这意味着,您的“isReady”变量将一次又一次地被覆盖为“false”。

为了防止这种情况并保留生命周期事件,您可以使用基本注入。

例如,将“LaunchView”更改为:

struct LaunchView: View {
    
    // Change here
    @ObservedObject var control: LaunchViewControl
    
    var body: some View {
        NavigationView(content: {
            ZStack {
                //some view hierarchy
                Text("Launch")
                NavigationLink(destination: RootView(),
                               isActive: $control.isReady,
                               label: {EmptyView()})

            }
            .onAppear(perform: {
                control.loadData()
            })
            .navigationBarTitle("")
            .navigationBarHidden(true)
            .statusBar(hidden: true)
        }).navigationViewStyle(StackNavigationViewStyle())
    }
}
Run Code Online (Sandbox Code Playgroud)

以及您的“Jam500App”:

@main
struct Jam500App: App {
    
    @Environment(\.scenePhase) var scenePhase
    
    let launchViewControl = LaunchViewControl()
    
    var body: some Scene {
        WindowGroup {
            // Inject launchViewControl
            LaunchView(control: launchViewControl)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

您现在应该看到第二个视图没有弹出,并且您还保留了生命周期事件。

2. 状态对象

您可以将 ObservedObject 更改为“StateObject”,而不是执行所有这些操作,这将在视图被丢弃和重绘时保持其状态。

更多信息

另外,SO 中已经有一些很好的答案,您可以检查 ObservedObject 及其工作原理。其中之一就是这个。关于 StateObject 和 ObservedObject 之间的另一个区别是这个

为了防止在项目的进一步步骤中出现这种情况,我会尝试 MVVM 方法,如果您想了解更多相关信息,那里有很棒的教程,例如这个.