SwiftUI非模态ErrorView显示2秒后隐藏:多次点击重置失效问题
SwiftUI全局非模态ErrorView多次点击异常修复
问题根源
当用户多次点击触发按钮时,旧的ErrorView实例会被快速销毁,但它内部的DispatchQueue.main.asyncAfter任务仍可能在后台等待执行,导致新的错误提示要么被提前隐藏,要么旧任务无法正确设置errorWrapper = nil,最终出现显示逻辑混乱的问题。
解决方案1:ErrorView内部管理定时器(轻量修复)
给ErrorView添加定时器存储,在视图销毁时取消未执行的任务,避免旧任务干扰新的错误提示。
修改后的ErrorView代码:
struct ErrorView: View { @State private var hidden: Bool = false @Binding var errorWrapper: ErrorWrapper? @State private var timer: Timer? var body: some View { VStack { Text(errorWrapper?.error.localizedDescription ?? "") Text(errorWrapper?.guidance ?? "") } .onAppear { // 创建定时器,2秒后执行隐藏逻辑 timer = Timer.scheduledTimer(withTimeInterval: 2.0, repeats: false) { _ in withAnimation { hidden = true errorWrapper = nil } } } .onDisappear { // 视图销毁时取消定时器,终止未执行的任务 timer?.invalidate() timer = nil } .foregroundStyle(.white) .padding() .background(.red) .clipShape(RoundedRectangle(cornerRadius: 16.0, style: .continuous)) .opacity(hidden ? 0 : 1) } }
解决方案2:容器视图统一管理逻辑(更可控)
将自动隐藏的逻辑移到ContentViewContainer中,利用SwiftUI的Task处理延迟,每次触发错误时重置任务,确保只有最新的错误提示会在2秒后隐藏。
修改后的ContentViewContainer代码:
struct ContentViewContainer: View { @State private var errorWrapper: ErrorWrapper? @State private var hideTask: Task<Void, Never>? private func showError(_ error: Error, guidance: String) { errorWrapper = ErrorWrapper(error: error, guidance: guidance) // 取消之前的隐藏任务,避免干扰新提示 hideTask?.cancel() // 创建新的延迟隐藏任务 hideTask = Task { try? await Task.sleep(nanoseconds: 2_000_000_000) await MainActor.run { withAnimation { errorWrapper = nil } } } } var body: some View { ContentView() .environment(\.showError, ShowErrorAction(action: showError)) .frame(maxWidth: .infinity, maxHeight: .infinity) .background(.green) .overlay(alignment: .bottom) { if let errorWrapper = errorWrapper { ErrorView(errorWrapper: $errorWrapper) } } } }
简化后的ErrorView代码:
struct ErrorView: View { @Binding var errorWrapper: ErrorWrapper? var body: some View { VStack { Text(errorWrapper?.error.localizedDescription ?? "") Text(errorWrapper?.guidance ?? "") } .foregroundStyle(.white) .padding() .background(.red) .clipShape(RoundedRectangle(cornerRadius: 16.0, style: .continuous)) .transition(.opacity) } }
方案对比
- 方案1:改动范围小,适合快速修复,逻辑封装在
ErrorView内部,保持视图独立性。 - 方案2:将状态和逻辑集中在容器层,更符合SwiftUI状态管理规范,便于后续扩展(比如添加手动关闭按钮、调整延迟时长等)。
内容的提问来源于stack exchange,提问作者john doe
相关产品推荐
相关产品推荐

