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

SwiftUI表格数据量变更时动画性能低下的优化求助

问题

我的应用里,侧边栏是表格的数据筛选器,详情视图展示表格本身。在侧边栏选择筛选项时,表格视觉更新存在明显延迟。通过性能分析发现问题出在表格行高的计算上。我尝试使用.fixedSize(horizontal: false, vertical: true)固定所有表格列视图的高度,但并未解决问题。

由于所有行的高度是预先已知且一致的,是否存在强制表格不重新计算行高的方法?

模型代码

@Observable
final class Item: Identifiable {
    var id: UUID
    var value: Int
    var name: String
    var c1: String
    var c2: String
    var c3: String
    var c4: String
    var c5: String
    var c6: String
    var c7: String
    var c8: String
    var c9: String
    
    init(value: Int) {
        self.id = UUID()
        self.value = value
        self.name = "name_" + "\(Int.random(in: 0...9))"
        self.c1 = "\(UUID())"
        self.c2 = "\(UUID())"
        self.c3 = "\(UUID())"
        self.c4 = "\(UUID())"
        self.c5 = "\(UUID())"
        self.c6 = "\(UUID())"
        self.c7 = "\(UUID())"
        self.c8 = "\(UUID())"
        self.c9 = "\(UUID())"

    }
}
@MainActor
@Observable
final class AppModel {
    
    var items: [Item]
    var selectedItem1: Set<UUID> = .init()
    
    var selectedFilters: Set<String> = []
    
    var subject: CurrentValueSubject<Int, Never> = .init(0)
    
    init() {
        var items: [Item] = []
        for i in 0...1000 {
            items.append(Item(value: i))
        }
        self.items = items
    }
    
    func updateValue() {
        let index = Int.random(in: 0..<items.count)
        items[index].value = Int.random(in: 200...300)
    }
    
    func startPolling() {
        // 模拟网络轮询
        Task {
            let asymcSequence = subject
                .debounce(for: .seconds(1), scheduler: DispatchQueue.global())
                .values
            
            for await _ in asymcSequence {
                updateValue()
                subject.send(subject.value + 1)
            }
        }
    }
}

视图代码

struct ContentView: View {
    
    @Environment(AppModel.self) private var appModel
    @State private var sortOrder = [KeyPathComparator(\Item.name)]
    
    private var sideBarItems: [String] {
        let set = Set(appModel.items.map({ $0.name })).sorted(by: <)
        return Array(set)
    }
    
    private var items: [Item] {
        appModel.items.filter({ appModel.selectedFilters.contains($0.name )})
    }
    
    var body: some View {
        @Bindable var appModel = appModel
        NavigationSplitView {
            List(selection: $appModel.selectedFilters) {
                ForEach(sideBarItems, id: \.self) { item in
                    Text(item)
                        .tag(item)
                        .badge(appModel.items.filter({ $0.name == item }).count)
                    
                }
            }
        } detail: {
            Table(of: Item.self, selection: $appModel.selectedItem1, sortOrder: $sortOrder) {
                TableColumn("value") { item in
                    Text("\(item.value)")
                        //.fixedSize(horizontal: false, vertical: true)
                }
                TableColumn("c1", value: \.c1)
                TableColumn("c2", value: \.c2)
                TableColumn("c3", value: \.c3)
                TableColumn("c4", value: \.c4)
                TableColumn("c5", value: \.c5)
                TableColumn("c6", value: \.c6)
                TableColumn("c7", value: \.c7)
                TableColumn("c8", value: \.c8)
                TableColumn("c9", value: \.c9)
            } rows: {
                ForEach(items)
            }
        }
        .animation(.smooth, value: items.count)
        .frame(minWidth: 1000, minHeight: 640)
        .padding()
        .onAppear {
            appModel.startPolling()
        }
    }
}

解决方案

1. 强制表格使用固定行高

SwiftUI的Table默认会自动计算每一行的高度,要跳过这个计算,可以给每个TableRow设置固定高度,并移除默认内边距避免高度偏差:

// 修改表格的rows部分
rows: {
    ForEach(items) { item in
        TableRow(item)
            .frame(height: 44) // 设置你需要的固定行高
            .tableRowInsets(EdgeInsets()) // 移除默认内边距
    }
}

2. 缓存筛选结果,减少重复计算

当前视图中的items计算属性每次筛选都会遍历全量数据,数据量大时会加重性能负担。可以在AppModel中缓存筛选后的结果:

// 在AppModel中添加
var filteredItems: [Item] = []

func updateFilteredItems() {
    filteredItems = items.filter { selectedFilters.contains($0.name) }
}

然后在视图中监听筛选条件变化,触发更新:

// 在ContentView的body中添加
.onChange(of: appModel.selectedFilters) {
    appModel.updateFilteredItems()
}

之后直接使用appModel.filteredItems作为表格的数据源,替代原来的计算属性。

3. 移除不必要的全局动画

当前.animation(.smooth, value: items.count)会对整个NavigationSplitView应用动画,这会额外增加渲染负担。如果不需要这个动画可以直接移除;如果需要局部动画,仅给侧边栏或表格添加针对性动画即可。

4. 固定表格列的内容布局

对于使用value:初始化的表格列,设置内容不换行,避免表格因内容变化重新计算行高:

TableColumn("c1", value: \.c1)
    .fixedSize(horizontal: true, vertical: false)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 03:24:56