Swift Charts:如何通过Toggle切换显示不同图表?
SwiftUI Chart切换显示时添加RuleMark/PointMark出现编译错误的问题
我想用Toggle切换显示两种不同的图表,简单版本代码能正常运行,但给图表加上RuleMark、PointMark这类元素后,就出现了“Compiler is unable to... try breaking up the expression into distinct sub-expressions”编译错误。想问下是不是主View里的条件语句处理方式有问题?
可正常运行的简单版本代码
Chart { if isLineGraph == true { ForEach(cloudCoverArray, id:\.self) {item in LineMark( x: .value("Date", item.date), y: .value("CloudCover", item.averageCloud), series: .value("Date", "CloudCover") ) .foregroundStyle(Color.gray) .lineStyle(StrokeStyle(lineWidth: 2)) } } else { ForEach(cloudCoverArray, id:\.self) {item in LineMark( x: .value("Date", item.date), y: .value("CloudCover", item.coverCloud), series: .value("Date", "CloudCover") ) .foregroundStyle(Color.gray) .lineStyle(StrokeStyle(lineWidth: 1)) } } }
添加RuleMark/PointMark后的错误代码
Chart { if isLineGraph == true { ForEach(cloudCoverArray, id:\.self) {item in LineMark( x: .value("Date", item.date), y: .value("CloudCover", item.averageCloud), series: .value("Date", "CloudCover") ) .foregroundStyle(Color.gray) .lineStyle(StrokeStyle(lineWidth: 2)) } RuleMark(x: .value("Date", cloudCoverArray[Int(daySlider)].date)) .foregroundStyle(Color.mint) .lineStyle(StrokeStyle(lineWidth: 1)) PointMark( x: .value("Date", cloudCoverArray[Int(daySlider)].date), y: .value("Distance", cloudCoverArray[Int(daySlider)].distance) ) .foregroundStyle(.orange) } else { ForEach(cloudCoverArray, id:\.self) {item in LineMark( x: .value("Date", item.date), y: .value("CloudCover", item.coverCloud), series: .value("Date", "CloudCover") ) .foregroundStyle(Color.gray) .lineStyle(StrokeStyle(lineWidth: 1)) } RuleMark(x: .value("Date", cloudCoverArray[Int(daySlider)].date)) .foregroundStyle(Color.mint) .lineStyle(StrokeStyle(lineWidth: 1)) PointMark( x: .value("Date", cloudCoverArray[Int(daySlider)].date), y: .value("Distance", cloudCoverArray[Int(daySlider)].distance) ) .foregroundStyle(.orange) } }
解决方法
这个编译错误确实和主View里的条件语句处理有关——SwiftUI的Chart在处理包含多个Mark的复杂条件分支时,类型推断会遇到瓶颈。可以通过以下两种方式解决:
方式一:把分支内容抽成独立View
将两个分支里的图表内容分别封装成单独的View,让编译器能明确每个分支的类型:
// 替换成你实际的数据类型 struct CloudData: Identifiable { let id = UUID() let date: Date let averageCloud: Double let coverCloud: Double let distance: Double } struct AverageCloudChart: View { let cloudCoverArray: [CloudData] let selectedItem: CloudData var body: some View { ForEach(cloudCoverArray) {item in LineMark( x: .value("Date", item.date), y: .value("CloudCover", item.averageCloud), series: .value("Date", "CloudCover") ) .foregroundStyle(Color.gray) .lineStyle(StrokeStyle(lineWidth: 2)) } RuleMark(x: .value("Date", selectedItem.date)) .foregroundStyle(Color.mint) .lineStyle(StrokeStyle(lineWidth: 1)) PointMark( x: .value("Date", selectedItem.date), y: .value("Distance", selectedItem.distance) ) .foregroundStyle(.orange) } } struct CoverCloudChart: View { let cloudCoverArray: [CloudData] let selectedItem: CloudData var body: some View { ForEach(cloudCoverArray) {item in LineMark( x: .value("Date", item.date), y: .value("CloudCover", item.coverCloud), series: .value("Date", "CloudCover") ) .foregroundStyle(Color.gray) .lineStyle(StrokeStyle(lineWidth: 1)) } RuleMark(x: .value("Date", selectedItem.date)) .foregroundStyle(Color.mint) .lineStyle(StrokeStyle(lineWidth: 1)) PointMark( x: .value("Date", selectedItem.date), y: .value("Distance", selectedItem.distance) ) .foregroundStyle(.orange) } } // 主View中调用 Chart { if isLineGraph { AverageCloudChart(cloudCoverArray: cloudCoverArray, selectedItem: cloudCoverArray[Int(daySlider)]) } else { CoverCloudChart(cloudCoverArray: cloudCoverArray, selectedItem: cloudCoverArray[Int(daySlider)]) } }
方式二:提取重复代码,简化分支结构
注意到两个分支里的RuleMark和PointMark完全重复,可以把这部分代码移到条件分支外面,只在分支中处理不同的LineMark,减轻编译器的推断压力:
Chart { if isLineGraph { ForEach(cloudCoverArray, id:\.self) {item in LineMark( x: .value("Date", item.date), y: .value("CloudCover", item.averageCloud), series: .value("Date", "CloudCover") ) .foregroundStyle(Color.gray) .lineStyle(StrokeStyle(lineWidth: 2)) } } else { ForEach(cloudCoverArray, id:\.self) {item in LineMark( x: .value("Date", item.date), y: .value("CloudCover", item.coverCloud), series: .value("Date", "CloudCover") ) .foregroundStyle(Color.gray) .lineStyle(StrokeStyle(lineWidth: 1)) } } // 重复标记移到分支外 RuleMark(x: .value("Date", cloudCoverArray[Int(daySlider)].date)) .foregroundStyle(Color.mint) .lineStyle(StrokeStyle(lineWidth: 1)) PointMark( x: .value("Date", cloudCoverArray[Int(daySlider)].date), y: .value("Distance", cloudCoverArray[Int(daySlider)].distance) ) .foregroundStyle(.orange) }
额外优化建议
提前处理索引取值,在View中计算一个selectedItem属性,避免在Chart闭包中直接做类型转换和索引操作,进一步降低编译器的推断负担:
// 主View中添加计算属性 private var selectedItem: CloudData { cloudCoverArray[Int(daySlider)] } // 后续在Chart中直接使用 RuleMark(x: .value("Date", selectedItem.date))
内容的提问来源于stack exchange,提问作者Edward Hasted
相关产品推荐
相关产品推荐

