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

如何判断SwiftUI视图转场/动画已完成?

SwiftUI 等待视图转场完成后展示模态框的优雅方案

问题场景

关闭Sheet A后从View A转场至View B,若直接在View B初始化时绑定状态展示Sheet B,会触发以下错误:

[Presentation] Attempt to present <TtGC7SwiftUI29PresentationHostingControllerVS_7AnyView: 0x107a03a00> on <TtGC7SwiftUI19UIHostingControllerGVS_15ModifiedContentVS_7AnyViewVS_12RootModifier_: 0x107821800> (from <TtGC7SwiftUI19UIHostingControllerVVS_19BridgedPresentation8RootView: 0x107a1a200>) while a presentation is in progress.

这是因为View B的转场动画尚未完成,此时触发新的模态框展示会导致冲突。以下是两种比硬编码延迟更可靠的解决方案:


方案一:利用withAnimation的Completion回调(iOS 15+)

SwiftUI从iOS 15开始支持withAnimation的完成闭包,可精准监听转场动画结束时机:

struct ContentView: View {
    @State private var showSheetA = false
    @State private var showViewB = false
    @State private var showSheetB = false
    
    var body: some View {
        ZStack {
            if !showViewB {
                ViewA()
                    .sheet(isPresented: $showSheetA) {
                        SheetA(onButtonTap: handleSheetAButtonTap)
                    }
            } else {
                ViewB()
                    .sheet(isPresented: $showSheetB) {
                        SheetB()
                    }
            }
        }
    }
    
    private func handleSheetAButtonTap() {
        // 先关闭Sheet A
        showSheetA = false
        // 执行View A到View B的转场动画,完成后触发Sheet B
        withAnimation {
            showViewB = true
        } completion: {
            showSheetB = true
        }
    }
}

// 示例子视图
struct ViewA: View {
    var body: some View {
        Text("View A")
            .onTapGesture {
                showSheetA = true
            }
    }
}

struct SheetA: View {
    let onButtonTap: () -> Void
    
    var body: some View {
        Button("关闭并转场到View B,展示Sheet B") {
            onButtonTap()
        }
    }
}

struct ViewB: View {
    var body: some View {
        Text("View B(Sheet B的背景)")
    }
}

struct SheetB: View {
    var body: some View {
        Text("Sheet B")
    }
}

这种方案完全基于SwiftUI原生API,无需依赖UIKit,逻辑清晰且时机精准。


方案二:自定义Modifier监听转场完成(兼容iOS 13+)

如果需要支持iOS 13及以下版本,可以通过自定义ViewModifier结合UIKit的动画监听来实现:

// 自定义Modifier,监听视图转场完成
struct OnTransitionCompletedModifier: ViewModifier {
    let action: () -> Void
    
    func body(content: Content) -> some View {
        content
            .background(TransitionCompletionView(action: action))
    }
}

// 借助UIViewRepresentable监听动画完成
struct TransitionCompletionView: UIViewRepresentable {
    let action: () -> Void
    
    func makeUIView(context: Context) -> UIView {
        let view = UIView()
        view.isHidden = true
        return view
    }
    
    func updateUIView(_ uiView: UIView, context: Context) {
        // 通过空动画触发完成回调,监听视图转场结束
        UIView.animate(withDuration: 0) {} completion: { _ in
            DispatchQueue.main.async {
                self.action()
            }
        }
    }
}

// 给View添加扩展方法,方便调用
extension View {
    func onTransitionCompleted(action: @escaping () -> Void) -> some View {
        modifier(OnTransitionCompletedModifier(action: action))
    }
}

// 使用示例
struct ContentView: View {
    @State private var showSheetA = false
    @State private var showViewB = false
    @State private var showSheetB = false
    
    var body: some View {
        ZStack {
            if !showViewB {
                ViewA()
                    .sheet(isPresented: $showSheetA) {
                        SheetA(onButtonTap: {
                            showSheetA = false
                            withAnimation {
                                showViewB = true
                            }
                        })
                    }
            } else {
                ViewB()
                    .onTransitionCompleted {
                        showSheetB = true
                    }
                    .sheet(isPresented: $showSheetB) {
                        SheetB()
                    }
            }
        }
    }
}

这种方案通过UIKit的动画回调机制,能准确捕捉到View B转场完成的时机,兼容更低版本的iOS系统。


方案对比

  • 硬编码延迟(asyncAfter):依赖固定时间值,不同设备/系统版本的动画时长不一致,容易出现报错或等待过长的问题。
  • Sheet内导航:无法让View B作为Sheet B的背景提供上下文,不符合需求。
  • 上述两种方案:精准监听转场完成时机,避免冲突,同时满足View B作为背景的需求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 21:21:49