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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 21:43:11