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() } }
代码说明
- AnimationViewModel:统一管理视图的显隐状态,封装动画触发逻辑,确保添加/移除时的动画同步。
- AnimatedWrapper:作为通用包装器,接收任意视图内容,通过
if vm.isVisible控制内容的显隐,触发子视图的transition动画。 - Example视图:修改
pushView和popView逻辑,移除时先调用包装器的隐藏方法,等待动画完成后再从数组中移除元素,保证移除转场完整展示。
内容的提问来源于stack exchange,提问作者Mattijs
相关产品推荐
相关产品推荐

