SwiftUI List中transition自定义滑动动画始终为默认效果
问题根因
自定义转场不生效的核心原因:
- List 底层基于
UITableView实现,自带行插入、删除、刷新的默认系统动画,会直接覆盖开发者自定义的transition效果 - 未通过
id修饰符强制触发视图的插入/移除流程,SwiftUI 会默认做视图内内容刷新,不会触发转场逻辑 - 原代码只写了固定方向的
move转场,没有根据标签切换方向动态调整进出的滑动方向,即使生效也无法实现左滑切右侧标签、右滑切左侧标签的交互效果
可直接复用的修复代码
修复逻辑:
- 新增状态变量记录上一次选中的标签,用于判断滑动方向
- 给标签内容容器加
id(selectedTab),强制选中项变化时销毁旧视图、插入新视图,触发转场 - 用非对称转场
asymmetric根据切换方向分别设置插入、移除的滑动边 - 通过
transaction屏蔽List默认行动画的干扰
完整修改后的核心代码:
import SwiftUI import Combine struct GameTabView: View { @State var selectedTab: Int = 0 // 记录上一次选中的tab,用于判断滑动方向 @State private var previousSelectedTab: Int = 0 init() { UITableView.appearance().sectionHeaderTopPadding = 0 } var body: some View { listView .ignoresSafeArea() } var listView: some View { List { Group { Color.gray.frame(height: 400) sectionView } .listRowInsets(EdgeInsets()) } .listStyle(.plain) } var sectionView: some View { Section { tabContentView .id(selectedTab) // 强制id变更时重建视图,触发转场 .transition( .asymmetric( insertion: .move(edge: selectedTab > previousSelectedTab ? .trailing : .leading), removal: .move(edge: selectedTab > previousSelectedTab ? .leading : .trailing) ) ) .background(Color.blue) // 拦截List默认动画,强制使用自定义转场动画 .transaction { transaction in transaction.animation = .easeInOut(duration: 0.25) } } header: { headerView } } private var headerView: some View { ScrollViewReader { proxy in ScrollView(.horizontal, showsIndicators: false) { HStack(spacing: 16) { Button { previousSelectedTab = selectedTab withAnimation(.easeInOut(duration: 0.25)) { selectedTab = 0 } } label: { Text("AAAA") .padding() } Button { previousSelectedTab = selectedTab withAnimation(.easeInOut(duration: 0.25)) { selectedTab = 1 } } label: { Text("BBBB") .padding() } Button { previousSelectedTab = selectedTab withAnimation(.easeInOut(duration: 0.25)) { selectedTab = 2 } } label: { Text("CCCC") .padding() } } } } .background(Color.green) } @ViewBuilder private var tabContentView: some View { switch selectedTab { case 0: DummyScreen(title: "FIRST", color: .red) case 1: DummyScreen(title: "SECOND", color: .green) case 2: DummyScreen(title: "THIRD", color: .blue) default: EmptyView() } } } struct DummyScreen: View { let title: String let color: Color var body: some View { VStack { ForEach(0..<15, id: \.self) { index in HStack { Text("#\(index): title \(title)") .foregroundColor(Color.black) .font(.system(size: 30)) .padding(.vertical, 20) Spacer() } .background(Color.yellow) } } .background(color) } }
问题效果演示

如果在iOS15及以下版本运行仍存在默认动画干扰,可以把标签内容区从List Row移出,改用
ScrollView + LazyVStack实现列表布局,完全绕过UITableView的内置动画逻辑,转场即可100%生效。
内容的提问来源于stack exchange,提问作者Abhishek Thapliyal
相关产品推荐
相关产品推荐

