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

为何SwiftUI Chart无法生成并列分组柱状图?

分组柱状图无法并列显示的原因分析与修复方案

问题场景

在iOS 16及以上版本的SwiftUI中,尝试通过position(by:)修饰符实现Burned和Consumed两类柱状图的并列分组显示,但最终两类柱状图在同一日期位置重叠,未达到预期的分组效果。

原代码

enum SwiftChartEnergyType {
    case Burned
    case Consumed
}

struct SwiftChartEnergyObject: Identifiable {
    let id = UUID()
    let energy: Int
    let date: Date
    var swiftChartEnergyType: SwiftChartEnergyType
}

@available(iOS 16.0, *)
struct WeeklyEnergyGroupSWIFTBarChart: View {
    var energyDays: [EnergyDay] = []
    var dietaryDays: [DietaryDay] = []
    
    var body: some View {
        VStack {
            Chart(prepareDatForChart(), id: \.energyType) { item in
                ForEach(item.data) {
                    BarMark(
                        x: .value("Date", $0.date),
                        y: .value("Energy", $0.energy)
                    )
                    .foregroundStyle(by: .value("Energy Type", item.energyType))
                    .position(by: .value("Energy Type", item.energyType)) // 未实现并列分组
                }
            }
            .chartYAxis {
                AxisMarks(position: .leading)
            }
            .chartForegroundStyleScale([
                "Burned" : TrackerConstants.AppleFitnessOrange,
                "Consumed": TrackerConstants.AppleFitnessGreen
            ])
        }
    }
    
    private func prepareDatForChart() -> [(energyType: String, data: [SwiftChartEnergyObject])] {
        let data = [
            (energyType: "Burned", data: energyDays.map { SwiftChartEnergyObject(energy: $0.totalEnergyBurned, date: $0.date, swiftChartEnergyType: .Burned) } ),
            (energyType: "Consumed", data: dietaryDays.map { SwiftChartEnergyObject(energy: $0.totalCalories, date: $0.date, swiftChartEnergyType: .Consumed) } )

        ]
        
        return data
    }
}

原因排查

  1. 数据结构嵌套导致Chart无法识别分组关联
    原代码将数据按类型拆分为两个分组的元组数组,外层Chart遍历分组,内层ForEach遍历每个分组的数据。这种嵌套结构下,Chart会将每个分组的BarMark视为独立的数据集,无法识别同一日期下的两个数据点属于同一分组单元,因此无法触发position(by:)的分组偏移逻辑。

  2. position(by:)的使用层级错误
    position(by:)需要作用在同一层级的BarMark上,让Chart能针对同一X轴值(日期)下的不同类别数据进行位置计算。但原代码的嵌套ForEach使得每个BarMark属于不同的分组上下文,Chart无法将它们关联为同一日期下的分组元素,修饰符无法生效。

修复方案

步骤1:重构数据源为扁平数组

将两类数据合并为一个扁平的SwiftChartEnergyObject数组,确保同一日期的两类数据都在同一个数据集里,让Chart能一次性遍历所有数据点。

步骤2:调整Chart结构

直接遍历扁平后的数组,移除嵌套的ForEach,让每个BarMark处于同一层级,同时正确使用position(by:)指定类别字段。

修改后的代码

enum SwiftChartEnergyType: String { // 改为String类型,方便直接作为图例文本
    case Burned = "Burned"
    case Consumed = "Consumed"
}

struct SwiftChartEnergyObject: Identifiable {
    let id = UUID()
    let energy: Int
    let date: Date
    var swiftChartEnergyType: SwiftChartEnergyType
}

@available(iOS 16.0, *)
struct WeeklyEnergyGroupSWIFTBarChart: View {
    var energyDays: [EnergyDay] = []
    var dietaryDays: [DietaryDay] = []
    
    var body: some View {
        VStack {
            Chart(prepareDatForChart()) { item in
                BarMark(
                    x: .value("Date", item.date),
                    y: .value("Energy", item.energy)
                )
                .foregroundStyle(by: .value("Energy Type", item.swiftChartEnergyType.rawValue))
                .position(by: .value("Energy Type", item.swiftChartEnergyType.rawValue)) // 同一层级下生效
            }
            .chartYAxis {
                AxisMarks(position: .leading)
            }
            .chartForegroundStyleScale([
                SwiftChartEnergyType.Burned.rawValue : TrackerConstants.AppleFitnessOrange,
                SwiftChartEnergyType.Consumed.rawValue: TrackerConstants.AppleFitnessGreen
            ])
        }
    }
    
    private func prepareDatForChart() -> [SwiftChartEnergyObject] {
        let burnedData = energyDays.map { 
            SwiftChartEnergyObject(energy: $0.totalEnergyBurned, date: $0.date, swiftChartEnergyType: .Burned) 
        }
        let consumedData = dietaryDays.map { 
            SwiftChartEnergyObject(energy: $0.totalCalories, date: $0.date, swiftChartEnergyType: .Consumed) 
        }
        // 合并为扁平数组
        return burnedData + consumedData
    }
}

关键说明

  • 把SwiftChartEnergyType改为String枚举,直接用rawValue作为图例文本,避免手动拼接字符串。
  • 数据源合并为扁平数组后,Chart可以识别同一日期下的两个数据点,position(by:)会自动计算每个类别柱状图的偏移量,实现并列分组效果。
  • 移除嵌套的ForEach,让所有BarMark处于同一层级,确保Chart能正确处理分组逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 22:35:41