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

SwiftUI嵌套分组List实现无限滚动出现数据重复如何解决

问题原因
  • 核心问题出在ForEach的唯一标识规则和分组数据的更新逻辑两个点:
    1. 如果你直接使用ForEach(..., id: \.self)遍历分组或订单,结构体实例的哈希值会随内部属性变化而改变,SwiftUI会把变更后的实例识别为全新元素,重复渲染旧内容
    2. 每次加载更多后全量重新生成分组数组时,若没有为分组设置稳定唯一标识,哪怕是同一日期的分组,新生成的结构体实例也会被判定为新Section,导致内容重复
    3. 加载更多的触发逻辑没有加锁,可能重复发起请求拉到重复数据(但你未分组时渲染正常,该问题概率较低)
修复方案

1. 定义稳定可识别的分组模型

不要依赖自动合成的Hashable/Identifiable,明确使用日期作为分组的唯一稳定标识,不受分组内订单变动影响:

// 首先保证订单模型遵循Identifiable,有全局唯一的订单ID
extension PurchaseOrderFragment: Identifiable {}

struct GroupedOrder: Identifiable {
    // 直接用日期字符串作为唯一ID,只要日期相同ID就相同,和分组内订单无关
    let id: String
    let date: Date
    var orders: [PurchaseOrderFragment]
    
    init(date: Date, orders: [PurchaseOrderFragment]) {
        self.date = date
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "yyyy-MM-dd"
        self.id = dateFormatter.string(from: date)
        self.orders = orders
    }
}

2. 修正数据更新逻辑

优先用增量更新逻辑,避免全量替换分组数组,减少不必要的视图重绘:

// 首先维护一个全局的未分组订单数组,做去重兜底
@Published var allOrders: [PurchaseOrderFragment] = []
@Published var groupedOrders: [GroupedOrder] = []
@Published var isLoading = false // 加载锁,避免重复请求

func loadMoreOrders() {
    guard !isLoading else { return }
    isLoading = true
    
    // 调用GraphQL接口拉取新订单
    fetchNewOrders { [weak self] newOrders in
        guard let self = self else { return }
        defer { self.isLoading = false }
        
        // 先对新订单做去重,避免后端返回重复数据
        let uniqueNewOrders = newOrders.filter { newOrder in
            !self.allOrders.contains { $0.id == newOrder.id }
        }
        // 追加到全局订单数组
        self.allOrders.append(contentsOf: uniqueNewOrders)
        
        // 对新订单按日期分组
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "yyyy-MM-dd"
        let newOrderGroups = Dictionary(grouping: uniqueNewOrders) { order in
            dateFormatter.string(from: order.purchaseDate)
        }
        
        // 增量更新分组数组
        for (dateStr, orders) in newOrderGroups {
            if let index = self.groupedOrders.firstIndex(where: { $0.id == dateStr }) {
                // 已有同日期分组,直接追加订单
                self.groupedOrders[index].orders.append(contentsOf: orders)
            } else {
                // 无同日期分组,新建分组加入数组(注意按你需要的日期顺序调整插入位置)
                guard let date = dateFormatter.date(from: dateStr) else { continue }
                let newGroup = GroupedOrder(date: date, orders: orders)
                self.groupedOrders.append(newGroup)
                // 如果需要按日期倒序排列,就用insert(_:at:)找对应的位置插入
            }
        }
    }
}

可选简化方案:如果你的订单总量不大(万条以下),不需要做增量更新,每次加载更多后直接基于allOrders全量重新生成分组数组即可,只要GroupedOrder的id稳定,就不会出现重复渲染问题。

3. 修正List渲染逻辑

使用基于Identifiable的遍历规则,不要用下标或\.self作为标识:

List {
    ForEach(groupedOrders) { group in
        Section(header: Text(group.date.formatted(date: .abbreviated, time: .omitted))) {
            ForEach(group.orders) { order in
                OrderCell(order: order)
                    .onAppear {
                        // 滚动到最后一个元素时触发加载更多
                        if order.id == group.orders.last?.id,
                           group.id == groupedOrders.last?.id {
                            loadMoreOrders()
                        }
                    }
            }
        }
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.28 07:45:03