[英]VStack messes with task modifier?
I'm creating a view called AsyncDataView
.我正在创建一个名为AsyncDataView
的视图。 The idea is, whenever the view appears, it will start an asynchronous Task
to download data from wherever.这个想法是,每当视图出现时,它将启动一个异步Task
以从任何地方下载数据。 While its in the process of downloading, the view should display a ProgressView
.在下载过程中,视图应显示ProgressView
。
Once the Task
is complete and the data has been downloaded, the view should display the downloaded data in a Text
view. Task
完成并下载数据后,视图应在Text
视图中显示下载的数据。
I thought this would be a fairly easy implementation, here's a minimal example:我认为这将是一个相当简单的实现,这是一个最小的例子:
struct ContentView: View {
let items = 0..<100
var body: some View {
NavigationView {
List(items, id: \.self) { _ in
// Removing the VStack resolves the issue!
VStack {
AsyncDataView()
}
}
Text("Middle Panel")
}
}
}
struct AsyncDataView: View {
@State private var data: String?
@ViewBuilder private var mainBody: some View {
if let data = data {
Text(data)
}
else {
ProgressView()
}
}
var body: some View {
mainBody
.task {
// Simulate loading data from a website or whatever
try? await Task.sleep(nanoseconds: 1_000_000_000)
// @State is thread safe according to Apple docs
data = String(Int.random(in: 0..<100))
}
}
}
Here, I just have a List
of 100 of these AsyncDataView
's.在这里,我只有 100 个AsyncDataView
的List
。 Whenever an AsyncDataView
appears, a Task
is started.每当出现AsyncDataView
时,都会启动一个Task
。 The Task
just sleeps 1 second to simulate downloading, and then assigns data. Task
只是休眠1秒模拟下载,然后分配数据。
Problem : Whenever I scroll down the List
really quickly, and then scroll back up, some items never finish loading.问题:每当我非常快速地向下滚动List
,然后向上滚动时,有些项目永远不会完成加载。 They just remain with the spinning ProgressView
.他们只是留在旋转的ProgressView
中。
It's best displayed with a video: https://imgur.com/a/ERH8477最好用视频展示: https://imgur.com/a/ERH8477
Or a GIF for convenience:或为方便起见的 GIF:
In the video, I wait for the initial items to be loaded.在视频中,我等待加载初始项目。 I then scroll down, and then back up.然后向下滚动,然后向上滚动。 At the end, you can see 4 items which never loaded.最后,您可以看到 4 个从未加载的项目。
The kicker : The weirdest thing is that if I remove the VStack
inside the body of the List
then this issue resolves!更重要的是:最奇怪的是,如果我删除List
主体内的VStack
,那么这个问题就解决了! It appears the VStack
messes with the task
modifier, Or, what I've also seen, is maybe the VStack
resets the @State
inside of the AsyncDataView
for some reason?看起来VStack
与task
修饰符混淆了,或者,我也看到, VStack
是否出于某种原因重置了@State
内部的AsyncDataView
?
I would just remove the currently redundant VStack
, but the issue is in my actual use-case I need to have another view displayed below this one.我只想删除当前多余的VStack
,但问题出在我的实际用例中,我需要在这个视图下方显示另一个视图。
Is anybody able to reproduce this, and does anybody know what's up with this code?有没有人能够重现这个,有没有人知道这段代码是怎么回事? Or is this a SwiftUI bug, as @Asperi suggested?或者这是一个 SwiftUI 错误,正如@Asperi 所建议的那样?
This was done on macOS Monterey 12.3.1 (21E258)
.这是在macOS Monterey 12.3.1 (21E258)
上完成的。
The solution appears to be platform dependent.该解决方案似乎依赖于平台。
A possible approach is to move async fetching data logic into view model and leave view update just on main actor.一种可能的方法是将异步获取数据逻辑移动到视图 model 中,并将视图更新仅保留在主要参与者上。
Tested with Xcode 13.3 / macOS 12.2使用 Xcode 13.3 / macOS 12.2 测试
Main part:主要部分:
class ViewModel: ObservableObject {
@Published var data: String?
init() {
update()
}
func update() {
Task.detached {
try? await Task.sleep(nanoseconds: 1_000_000_000)
await MainActor.run {
self.data = String(Int.random(in: 0..<100))
}
}
}
}
// ...
@StateObject private var vm = ViewModel()
var body: some View {
if let data = vm.data {
Text(data)
}
else {
ProgressView()
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.