我在 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 中遇到过这个问题吗?
您可以应用两种解决方案(我想到的,并且不会引起很多变化):
当您在视图中初始化“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)
您现在应该看到第二个视图没有弹出,并且您还保留了生命周期事件。
您可以将 ObservedObject 更改为“StateObject”,而不是执行所有这些操作,这将在视图被丢弃和重绘时保持其状态。
另外,SO 中已经有一些很好的答案,您可以检查 ObservedObject 及其工作原理。其中之一就是这个。关于 StateObject 和 ObservedObject 之间的另一个区别是这个。
为了防止在项目的进一步步骤中出现这种情况,我会尝试 MVVM 方法,如果您想了解更多相关信息,那里有很棒的教程,例如这个.
| 归档时间: |
|
| 查看次数: |
2514 次 |
| 最近记录: |