SwiftUI中ProgressView在任务完成后才显示的问题求助
SwiftUI点击ProjectItem无法提前显示ProgressView问题解决
问题描述
我有一个SwiftUI视图ProjectItem,点击时本该显示ProgressView作为加载提示,但实际只有在文档加载完成后才会显示。点击后打开文档需要几秒时间,我希望能立刻向用户展示加载状态。
相关代码
ProjectItem视图代码
struct ProjectItem: View { @EnvironmentObject var controller: ProjectController @State var showLoadingIcon: Bool = false let document: Document var body: some View { VStack { ZStack { Text(document.name).font(Interface.Text.PopoverDialogLabel) Text(document.editTime.toString(true)).font(.caption2).foregroundColor(.gray) if showLoadingIcon { ProgressView() } } .padding(Interface.Sizes.StandardPadding) .if(controller.editedDocumentID == nil) { $0.onTapGesture(count: 1, perform: { // Open Project showLoadingIcon = true // 这行代码实际在TransitionView之后执行 controller.openDocument(document: document) TransitionView() // 这行在ProgressView显示前执行 })} }
ProjectController中的openDocument方法
func openDocument(document: Document) { app.setProject(document.id) // 该操作耗时几秒,且当前必须运行在主线程 }
尝试过的无效方案
- 将
controller.openDocument(document: document)和TransitionView()包装进Task{} - 将
openDocument设为异步/同步调用
禁用TransitionView后通过断点发现,controller.openDocument执行完成后,才会检查showLoadingIcon的值并显示ProgressView。
解决方法
核心问题是app.setProject阻塞主线程,导致SwiftUI无法立即刷新视图更新showLoadingIcon状态。我们需要先让视图完成渲染,再执行耗时操作,同时修正TransitionView的展示逻辑。
修改后的onTapGesture逻辑
.if(controller.editedDocumentID == nil) { $0.onTapGesture(count: 1, perform: { // 1. 立即标记加载状态,触发视图刷新 showLoadingIcon = true // 2. 让主线程先完成视图渲染,再执行耗时操作 DispatchQueue.main.async { controller.openDocument(document: document) // 加载完成后可关闭加载状态(按需选择) showLoadingIcon = false // 3. 通过状态变量控制TransitionView展示,而非直接创建视图 controller.shouldShowTransition = true } })}
修正TransitionView的展示方式
直接在闭包里创建TransitionView()无法正确触发展示,需要通过状态变量控制:
- 在
ProjectController中添加状态:
class ProjectController: ObservableObject { // ...其他已有属性 @Published var shouldShowTransition = false }
- 在合适的位置(如父视图或当前视图的body中)添加:
if controller.shouldShowTransition { TransitionView() .onDisappear { controller.shouldShowTransition = false } }
原理说明
showLoadingIcon = true会立即触发SwiftUI的视图更新流程DispatchQueue.main.async将耗时操作推迟到当前主循环的下一轮执行,让SwiftUI有足够时间先渲染出ProgressView,再执行阻塞主线程的app.setProject
内容的提问来源于stack exchange,提问作者Osian
相关产品推荐
相关产品推荐

