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

SwiftUI Form嵌套多个可展开List时展开/折叠动画失效如何修复

SwiftUI Form内嵌List展开折叠动画失效修复方案

问题场景

开发含多Section的Form页面时,每个Section内放置支持展开/折叠额外行的独立List组件,将List嵌入Form容器后,List的展开、折叠过渡动画完全失效。

效果对比

  • 正常展开折叠动画效果:正常展开折叠动画
  • 嵌入Form后动画失效效果:嵌入Form后动画失效

问题复现代码

struct MyForm: View {
var items = [MyItem(text: "A"), MyItem(text: "B"), MyItem(text: "C"), MyItem(text: "D"), MyItem(text: "E"), MyItem(text: "F"), MyItem(text: "G"), MyItem(text: "H"), MyItem(text: "I")]

var body: some View {
        NavigationView {
            Form {
                Section(header: Text("Sector")) {
                    ExpandableList(items: items, visible: 4)
                }
                Section(header: Text("Region")) {
                    ExpandableList(items: items, visible: 4)
                }
            }.navigationTitle(Text("Filter"))
        }
    }
}


struct MyItem: Identifiable {
    let id = UUID()
    let text: String
}

struct ExpandableList: View {
    private var items: [MyItem]
    private var visible: Int
    private var visibleItems: [MyItem]

    @State var isExpanded = false

    init(items: [MyItem], visible: Int) {
        self.items = items
        self.visible = visible
    
        if visible < items.count {
            self.visibleItems = Array(items[0..<visible])
        } else {
            self.visibleItems = items
        }
    }

    var body: some View {
    
        List {
            ForEach(isExpanded ? items : visibleItems) { item in
                Text(item.text)
            }
        
            HStack {
                Button {
                    isExpanded.toggle()
                } label: {
                    if isExpanded {
                        Label("Show less", systemImage: "arrow.up")
                    } else {
                        Label("Show all", systemImage: "arrow.down")
                    }
                }
            }
        }.animation(.easeOut, value: isExpanded)
    }
}

问题根因

iOS端Form本质是基于UITableView封装的特殊列表容器,在Form内嵌套List会产生两层滚动容器冲突,系统会默认接管嵌套列表的行更新逻辑,覆盖自定义的动画配置,最终导致展开折叠动画失效。

修复方案

推荐方案(性能最优、动画最流畅)

移除ExpandableList内部冗余的List容器,直接使用ForEach实现行渲染——外层Form本身已经提供了列表行的布局能力,不需要额外嵌套List。同时将动画触发逻辑移到状态切换的withAnimation闭包中,显式触发行插入/删除的过渡效果。
修复后的ExpandableList代码:

struct ExpandableList: View {
    private var items: [MyItem]
    private var visible: Int
    private var visibleItems: [MyItem]

    @State var isExpanded = false

    init(items: [MyItem], visible: Int) {
        self.items = items
        self.visible = visible
    
        if visible < items.count {
            self.visibleItems = Array(items[0..<visible])
        } else {
            self.visibleItems = items
        }
    }

    var body: some View {
        // 移除外层嵌套List,直接返回行内容,由外层Form托管行布局
        ForEach(isExpanded ? items : visibleItems) { item in
            Text(item.text)
        }
    
        Button {
            // 显式用withAnimation包裹状态切换,触发行过渡动画
            withAnimation(.easeOut) {
                isExpanded.toggle()
            }
        } label: {
            if isExpanded {
                Label("Show less", systemImage: "arrow.up")
            } else {
                Label("Show all", systemImage: "arrow.down")
            }
        }
    }
}

备选方案(保留独立List结构)

如果业务需要保留List的独立样式,可通过禁用List滚动、设置固定高度消除容器冲突,代码如下:

struct ExpandableList: View {
    private var items: [MyItem]
    private var visible: Int
    private var visibleItems: [MyItem]

    @State var isExpanded = false

    init(items: [MyItem], visible: Int) {
        self.items = items
        self.visible = visible
    
        if visible < items.count {
            self.visibleItems = Array(items[0..<visible])
        } else {
            self.visibleItems = items
        }
    }

    var body: some View {
        List {
            ForEach(isExpanded ? items : visibleItems) { item in
                Text(item.text)
            }
        
            Button {
                withAnimation(.easeOut) {
                    isExpanded.toggle()
                }
            } label: {
                if isExpanded {
                    Label("Show less", systemImage: "arrow.up")
                } else {
                    Label("Show all", systemImage: "arrow.down")
                }
            }
        }
        .listStyle(.plain) // 取消List默认分组样式,避免和Form样式冲突
        .scrollDisabled(true) // 禁用内部List滚动,消除手势冲突
        .frame(height: listHeight) // 动态计算高度,完整展示所有内容
    }
    
    // 根据当前展示行数动态计算List高度
    private var listHeight: CGFloat {
        let rowCount = (isExpanded ? items.count : visibleItems.count) + 1 // 额外加1行展开/收起按钮
        return CGFloat(rowCount) * 44 // 44为系统默认列表行高,可根据自定义行高调整
    }
}

注意:备选方案需要根据实际自定义行高调整高度计算逻辑,性能和动画流畅度弱于第一种方案,优先推荐使用无嵌套的实现方式。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.02 04:48:23