简体   繁体   English

VStack 与任务修改器混淆?

[英]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 个AsyncDataViewList 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?看起来VStacktask修饰符混淆了,或者,我也看到, 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()
    }
}

Complete findings and code is here 完整的调查结果和代码在这里

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM