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

Ios 当应用程序被放到后台时,SwiftUI应用程序返回到第一个屏幕,ios,swiftui,Ios,Swiftui,我在SwiftUI中构建了一个应用程序,遇到了一个恼人的问题 当应用程序出现时,应用程序将显示一个自定义启动屏幕,同时应用程序的数据将异步加载。完成后,应用程序的主视图将显示给用户。一旦用户到达这一点,只要他们将应用程序拖回后台,导航堆栈就会毫无原因地跳回第一个启动屏幕 以下是相关的代码片段: MyApp.swift @main struct Jam500App: App { @Environment(\.scenePhase) var lifecycle init(

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

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

以下是相关的代码片段:

MyApp.swift

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

    init() {
        //initial setup goes here
    }
    
    var body: some Scene {
        WindowGroup {
                LaunchView()
        }
    }
    
}
启动视图

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())
    }
    
}
struct RootView: View {
    
    @ObservedObject var control = RootViewControl()
    
    var body: some View {
        HStack(spacing: 0) {
            //some view hierarchy
        }
        .navigationBarTitle("")
        .navigationBarHidden(true)
        .statusBar(hidden: true)
    }
    
}
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
        }
    }
    
}
RootView.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())
    }
    
}
struct RootView: View {
    
    @ObservedObject var control = RootViewControl()
    
    var body: some View {
        HStack(spacing: 0) {
            //some view hierarchy
        }
        .navigationBarTitle("")
        .navigationBarHidden(true)
        .statusBar(hidden: true)
    }
    
}
是否有其他人在Xcode 12.4中遇到过此问题?

有两种解决方案(我想到了,不会引起太多更改),您可以应用:

1.注射 在视图中初始化“LaunchViewControl”类时:

@ObservedObject var control: LaunchViewControl = LaunchViewControl()
SwiftUI在其视图被丢弃和重绘时会反复初始化此控制变量,因为ObservedObject与状态变量不同,SwiftUI不会跟踪它们的状态

要尝试此方法,请取消对“scenePhase”行的注释,并将“init”方法添加到“LaunchViewControl”,然后添加一些print语句以了解发生了什么,或者添加一个断点

例如:

final class LaunchViewControl: ObservableObject {
    // ....
    init() {
        print("initialized")
    }
    // ....
}
您将在控制台上看到多个“已初始化”日志。这意味着,“isReady”变量将一次又一次地被覆盖为“false”

要防止这种情况并保持生命周期事件,可以使用基本注入

例如,将“启动视图”更改为:

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())
    }
}
以及您的“Jam500App”到:

@main
struct Jam500App: App {
    
    @Environment(\.scenePhase) var scenePhase
    
    let launchViewControl = LaunchViewControl()
    
    var body: some Scene {
        WindowGroup {
            // Inject launchViewControl
            LaunchView(control: launchViewControl)
        }
    }
}
您现在应该看到第二个视图没有弹出,并且您还保留了生命周期事件

2.状态对象 您可以将ObservedObject更改为“StateObject”,而不是全部执行这些操作,当视图被丢弃并重新绘制时,它将保持其状态

更多信息 此外,还有一些很好的答案,您可以查看观察到的对象以及它们是如何工作的。其中之一是。另一个关于StateObject和ObservedObject之间的区别是

为了防止在项目的后续步骤中出现这种情况,我会尝试一下MVVM方法,如果您想进一步了解它,例如,这里有一些很棒的教程。

有两种解决方案(我想到了,并且不会引起太多更改),您可以应用:

1.注射 在视图中初始化“LaunchViewControl”类时:

@ObservedObject var control: LaunchViewControl = LaunchViewControl()
SwiftUI在其视图被丢弃和重绘时会反复初始化此控制变量,因为ObservedObject与状态变量不同,SwiftUI不会跟踪它们的状态

要尝试此方法,请取消对“scenePhase”行的注释,并将“init”方法添加到“LaunchViewControl”,然后添加一些print语句以了解发生了什么,或者添加一个断点

例如:

final class LaunchViewControl: ObservableObject {
    // ....
    init() {
        print("initialized")
    }
    // ....
}
您将在控制台上看到多个“已初始化”日志。这意味着,“isReady”变量将一次又一次地被覆盖为“false”

要防止这种情况并保持生命周期事件,可以使用基本注入

例如,将“启动视图”更改为:

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())
    }
}
以及您的“Jam500App”到:

@main
struct Jam500App: App {
    
    @Environment(\.scenePhase) var scenePhase
    
    let launchViewControl = LaunchViewControl()
    
    var body: some Scene {
        WindowGroup {
            // Inject launchViewControl
            LaunchView(control: launchViewControl)
        }
    }
}
您现在应该看到第二个视图没有弹出,并且您还保留了生命周期事件

2.状态对象 您可以将ObservedObject更改为“StateObject”,而不是全部执行这些操作,当视图被丢弃并重新绘制时,它将保持其状态

更多信息 此外,还有一些很好的答案,您可以查看观察到的对象以及它们是如何工作的。其中之一是。另一个关于StateObject和ObservedObject之间的区别是


为了防止在项目的后续步骤中出现这种情况,我会尝试一下MVVM方法,如果您想了解更多,例如,这里有很多很棒的教程。

只是为了检查一下,当您在Jam500App类中注释掉“@Environment(\.scenePhase)var lifecycle”行时,行为会发生变化,对吗?谢谢Burak,我从应用程序中删除了代码scenePhase,它成功了!这真的很烦人。如果没有它,我如何处理应用程序生命周期事件?只是想检查一下,当您在Jam500App类中注释掉“@Environment(\.scenePhase)var lifecycle”行时,行为会发生变化,对吗?谢谢Burak,我从应用程序中删除了代码scenePhase,它成功了!这真的很烦人。如果没有它,我如何处理应用程序生命周期事件?谢谢你,Burak,这是对我问题的一个很好的回答!谢谢你,Burak,这是对我问题的一个很好的回答!