【发布时间】:2021-12-20 22:59:53
【问题描述】:
我的应用程序使用 SwiftUI,以 iOS 15+ 为目标,并由 ObservableObject、AppState 管理,作为整个应用程序的真实来源。我使用@Published vars 来管理各个地方的覆盖状态(例如,加载数据时)没有问题。
简体AppState:
class AppState:ObservableObject {
@Published var showLoadingOverlay = false
func loadData() {
self.showLoadingOverlay = true
// fech data
self.showLoadingOverlay = false
}
}
在视图中,我通过@EnvironmentObject 注入AppState 并使用发布的变量来控制fullScreenCover。
简化示例:
struct MyDataView:View {
@EnvironmentObject var state:AppState
var body: some View {
ScrollView {
VStack {
ForEach(Array(zip(state.dataSets.indices, state.dataSets)), id: \.0) { (dataSetIndex, dataSet) in
MirrorChartView(dataSet: dataSet)
}
}
}
.onAppear {
state.loadData()
}
.fullScreenCover(isPresented: $state.showLoadingOverlay) {
LoadingOverlay()
}
}
}
问题我遇到的是在绑定值更改为false 时仍然显示一个叠加层。这种“卡住”仅发生在loadData() 完成速度非常快并且覆盖在完全显示之前实际上会被关闭的情况。
在视图中添加调试监视器确认与AppState 的绑定已正确传播:
struct MyDataView:View {
@EnvironmentObject var state:AppState
var body: some View {
VStack {
// draw stuff
}
.onAppear {
state.loadData()
}
.onChange(of: state.showLoadingOverlay) { showOverlay in
print("show overlay: \(showOverlay)")
}
.fullScreenCover(isPresented: $state.showLoadingOverlay) {
LoadingOverlay()
}
}
}
即当覆盖层向上滑动时,我可以看到日志show overlay: false,但它不会被关闭。
即使通过 @State 本地绑定添加间接也不能可靠地解决问题:
struct MyDataView:View {
@EnvironmentObject var state:AppState
@State var showLoadingOverlay = false
var body: some View {
VStack {
// draw stuff
}
.onAppear {
state.loadData()
}
.onChange(of: state.showLoadingOverlay) { showOverlay in
self.showLoadingOverlay = showOverlay
}
.fullScreenCover(isPresented: self.$showLoadingOverlay) {
LoadingOverlay()
}
}
}
更新:更多测试表明,即使就地绑定为 false 也不会关闭 fullScreenCover:
将状态绑定替换为就地自定义绑定显示我的状态已正确传播并导致视图更新。但是,工作表仍然存在。
struct MyDataView:View {
@EnvironmentObject var state:AppState
var body: some View {
VStack {
// draw stuff
}
.onAppear {
state.loadData()
}
.fullScreenCover(isPresented: .init(
get: {
print("recomputing binding: \(state.showOverlay)")
return state.showOverlay
}, set: { newValue in
state.showOverlay = newValue
}
)) {
LoadingOverlay()
}
}
}
这导致以下日志消息年表:
recomputing binding: false
recomputing binding: true
recomputing binding: false
更新:进一步的发现
我不知道该怎么做,但是在LoadingOverlay 的.onAppear() 中添加一条日志消息显示,它出现在之后绑定已更改为false:
struct MyDataView:View {
@EnvironmentObject var state:AppState
var body: some View {
ScrollView {
VStack {
ForEach(Array(zip(state.dataSets.indices, state.dataSets)), id: \.0) { (dataSetIndex, dataSet) in
MirrorChartView(dataSet: dataSet)
}
}
}
.onAppear {
state.loadData()
}
.onChange(of: state.showLoadingOverlay) { show in
print("show loading overlay \(show)")
}
.fullScreenCover(isPresented: $state.showLoadingOverlay) {
LoadingOverlay()
.onAppear {
print("loading overlay appears")
}
}
}
}
这导致以下日志消息年表:
show loading overlay: true
show loading overlay: false
loading overlay appears
有什么方法可以解决这个问题?
【问题讨论】:
-
我无法使用 code like this 复制它。你能举一个可重现的例子吗?
-
你能分享一下
fullscreenCover是什么吗? -
@SteveM 您的示例几乎就是正在发生的事情以及为什么我对这种行为如此困惑。在我的例子中,
loadData()使用combineLatest聚合来自两个源的数据并更新state中的另一个@Published var。由于我在整个应用程序的许多地方都使用了这种模式,因此我很难创建一个可重现的示例,即它适用于其他任何地方。但是,其他地方实际上调用 API 并且速度相对较慢,而在这种情况下,它只是使用发布者管道进行一些过滤和聚合,如果该数据已经加载,则基本上会立即发生。 -
@AvnerBarr
.fullScreenCover()中的代码只显示了一个 dump 加载覆盖,即只是一个带有一些文本的视图和一个ProgressView。加载覆盖本身既没有@State,也没有访问AppState。纯渲染。 -
@SteveM 我刚刚意识到您的示例与我正在执行的操作有所不同:加载是由
onAppear()触发的,而不是通过单击按钮触发的。不过这应该没什么区别。