Search code examples
swiftswiftuiswiftui-charts

Charts data in List cells changes when scrolling


I have a List in which each cell has a chart. The data for the list is generated once and stored in the chartData property. However, there is a bug. When scrolling the list, the charts in the cells change places: if we scroll the list a couple of cells down and then return to the top, then the first cell will have a completely different chart from what was before scrolling. However, only the chart changes, the number Text remains unchanged

struct ChartData: Identifiable {
    let id = UUID()
    let number: Int
    let data: [Double]

    var isPositiveTrend: Bool {
        return data.last ?? 0 > data.first ?? 0
    }
}

struct ContentView: View {
    let chartData: [ChartData] = (0..<90).map { index in
        ChartData(number: index, data: (0..<12).map { _ in Double.random(in: 1...10) })
    }

    var body: some View {
        List {
            ForEach(chartData) { data in
                HStack {
                    Text("\(data.number)")
                    Chart(Array(zip(data.data.indices, data.data)), id: \.0) { idx, val in
                        LineMark(
                            x: .value("index", idx),
                            y: .value("value", val)
                        )
                        .interpolationMethod(.catmullRom)
                        .foregroundStyle(data.isPositiveTrend ? .green : .red)
                    }
                    .chartYScale(domain: .automatic(includesZero: false))
                    .chartYAxis(.hidden)
                    .chartXAxis(.hidden)
                    .chartLegend(.hidden)
                }
            }
        }
    }
}

Solution

  • It seems that it is a bug appears from iOS 17.4 (See https://forums.developer.apple.com/forums/thread/750052)

    According to the discussion above:

    Adding .id(VALUE) to Chart(), where VALUE is unique per Chart()

    will solve the issue

    struct ChartData: Identifiable {
        let id = UUID()
        let number: Int
        let data: [Double]
    
        var isPositiveTrend: Bool {
            return data.last ?? 0 > data.first ?? 0
        }
    }
    
    struct ContentView: View {
        let chartData: [ChartData] = (0..<90).map { index in
            ChartData(number: index, data: (0..<12).map { _ in Double.random(in: 1...10) })
        }
    
        var body: some View {
            List {
                ForEach(chartData) { data in
                    HStack {
                        Text("\(data.number)")
                        Chart(Array(zip(data.data.indices, data.data)), id: \.0) { idx, val in
                            LineMark(
                                x: .value("index", idx),
                                y: .value("value", val)
                            )
                            .interpolationMethod(.catmullRom)
                            .foregroundStyle(data.isPositiveTrend ? .green : .red)
                        }
                        .id(data.id) // <--------  ADD THIS
                        .chartYScale(domain: .automatic(includesZero: false))
                        .chartYAxis(.hidden)
                        .chartXAxis(.hidden)
                        .chartLegend(.hidden)
                    }
                }
            }
        }
    }