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

SwiftUI中如何为容器子视图添加入/移除时的独立转场动画?

解决方案

问题根源

当你把Container作为整体添加/移除时,SwiftUI仅会对容器的根视图(HStack)应用转场动画。内部的Text子视图从未经历动态插入/移除的生命周期(它们始终存在于HStack中),所以你给子视图设置的transition不会被触发。

实现方案

通过一个通用的视图包装器,单独控制内部子视图的显隐状态,让子视图经历完整的插入/移除流程,从而激活各自的转场动画。同时在移除容器时,等待子视图的消失动画完成后再执行容器的移除操作。

完整代码示例

import SwiftUI

// 管理视图显隐状态的ViewModel
class AnimationViewModel: ObservableObject {
    @Published var isVisible = false
    
    func show() {
        withAnimation(.easeInOut(duration: 1)) {
            isVisible = true
        }
    }
    
    func hide(completion: @escaping () -> Void) {
        withAnimation(.easeInOut(duration: 1)) {
            isVisible = false
        }
        // 等待动画完成后执行回调
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
            completion()
        }
    }
}

// 通用动画包装器,支持任意类型视图
struct AnimatedWrapper<Content: View>: View, Identifiable {
    let id = UUID()
    private let content: () -> Content
    @StateObject private var vm = AnimationViewModel()
    
    init(@ViewBuilder content: @escaping () -> Content) {
        self.content = content
    }
    
    var body: some View {
        if vm.isVisible {
            content()
        }
        .onAppear {
            vm.show()
        }
    }
    
    // 对外暴露隐藏方法,用于触发移除动画
    func triggerHide(completion: @escaping () -> Void) {
        vm.hide(completion: completion)
    }
}

// 演示视图
struct Example: View {
    @State private var views: [AnimatedWrapper<AnyView>] = []
    
    func pushView(_ view: some View) {
        views.append(AnimatedWrapper { AnyView(view) })
    }
    
    func popView() {
        guard let lastView = views.last else { return }
        
        // 先触发子视图消失动画,完成后再移除容器
        lastView.triggerHide {
            DispatchQueue.main.async {
                views.removeLast()
            }
        }
    }

    var body: some View {
        VStack(spacing: 30) {
            Button("Add") {
                pushView(
                    HStack {
                        Text("First")
                            .transition(.move(edge: .leading))
                        Text("Second")
                            .transition(.move(edge: .trailing))
                    }
                )
            }

            VStack {
                ForEach(views) { wrapper in
                    wrapper
                }
            }

            Button("Remove") {
                popView()
            }
        }
        .padding()
    }
}

struct Example_Previews: PreviewProvider {
    static var previews: some View {
        Example()
    }
}

代码说明

  1. AnimationViewModel:统一管理视图的显隐状态,封装动画触发逻辑,确保添加/移除时的动画同步。
  2. AnimatedWrapper:作为通用包装器,接收任意视图内容,通过if vm.isVisible控制内容的显隐,触发子视图的transition动画。
  3. Example视图:修改pushView和popView逻辑,移除时先调用包装器的隐藏方法,等待动画完成后再从数组中移除元素,保证移除转场完整展示。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 20:25:17