You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI中.task与.refreshable刷新时请求被取消的问题

SwiftUI 刷新时出现 NSURLErrorDomain Code=-999 "cancelled" 问题解决

问题场景

使用SwiftUI开发时,通过.task modifier从远程拉取数据,后续用ScrollView.refreshable触发刷新操作,此时会抛出NSURLErrorDomain Code=-999 "cancelled"错误。即便改用模拟实现的PodcastProvider,该错误依然会复现,最终定位问题出在ProgressView的条件显示逻辑,但不清楚具体原因。

问题根源

当你通过if/else条件切换ProgressView和ScrollView的显示状态时,isLoading的变化会导致整个ScrollView被销毁并重新创建。而.refreshable是依附在ScrollView实例上的,当ScrollView被销毁时,系统会自动取消所有关联的未完成异步任务(包括刷新时的网络请求或模拟请求),从而触发-999错误。这个问题和实际网络请求无关,本质是视图层级变化导致的任务生命周期中断。

解决方案

不要通过条件替换整个ScrollView,而是用ZStack将ProgressView叠加在ScrollView上方,保持ScrollView的实例始终存在,避免任务被意外取消。

错误代码示例(触发问题)

struct PodcastListView: View {
    @State private var podcasts: [Podcast] = []
    @State private var isLoading = true
    private let provider = PodcastProvider()

    var body: some View {
        // if/else切换会导致ScrollView被销毁重建
        if isLoading {
            ProgressView("加载中...")
        } else {
            ScrollView {
                LazyVStack {
                    ForEach(podcasts) { podcast in
                        PodcastRow(podcast: podcast)
                    }
                }
            }
            .refreshable {
                isLoading = true
                try? await provider.refreshPodcasts()
                podcasts = provider.currentPodcasts
                isLoading = false
            }
            .task {
                isLoading = true
                await provider.loadInitialPodcasts()
                podcasts = provider.currentPodcasts
                isLoading = false
            }
        }
    }
}

修复后的代码示例

struct PodcastListView: View {
    @State private var podcasts: [Podcast] = []
    @State private var isLoading = true
    private let provider = PodcastProvider()

    var body: some View {
        ZStack(alignment: .center) {
            ScrollView {
                LazyVStack {
                    ForEach(podcasts) { podcast in
                        PodcastRow(podcast: podcast)
                    }
                }
            }
            .refreshable {
                isLoading = true
                try? await provider.refreshPodcasts()
                podcasts = provider.currentPodcasts
                isLoading = false
            }
            .task {
                isLoading = true
                await provider.loadInitialPodcasts()
                podcasts = provider.currentPodcasts
                isLoading = false
            }

            // ProgressView叠加在ScrollView上方,不破坏原有视图层级
            if isLoading {
                ProgressView("加载中...")
                    .background(Color.systemBackground.opacity(0.9))
                    .cornerRadius(8)
                    .padding()
            }
        }
    }
}

补充说明

  • 即使使用模拟的PodcastProvider也会复现错误,因为系统取消任务的逻辑和任务内容无关,只要异步任务绑定的视图实例被销毁,就会触发-999错误。
  • 另一种可选方案是将ProgressView放在ScrollView内部的顶部(比如通过if isLoading在LazyVStack最上方显示),同样可以避免ScrollView被销毁。

内容的提问来源于stack exchange,提问作者Valerio

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 04:19:59