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

SwiftUI List中transition自定义滑动动画始终为默认效果

问题根因

自定义转场不生效的核心原因:

  • List 底层基于UITableView实现,自带行插入、删除、刷新的默认系统动画,会直接覆盖开发者自定义的transition效果
  • 未通过id修饰符强制触发视图的插入/移除流程,SwiftUI 会默认做视图内内容刷新,不会触发转场逻辑
  • 原代码只写了固定方向的move转场,没有根据标签切换方向动态调整进出的滑动方向,即使生效也无法实现左滑切右侧标签、右滑切左侧标签的交互效果
可直接复用的修复代码

修复逻辑:

  1. 新增状态变量记录上一次选中的标签,用于判断滑动方向
  2. 给标签内容容器加id(selectedTab),强制选中项变化时销毁旧视图、插入新视图,触发转场
  3. 用非对称转场asymmetric根据切换方向分别设置插入、移除的滑动边
  4. 通过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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 06:03:23