SwiftUI Charts:基于时间绘制日期(让DateComponents遵循Plottable)
问题描述
希望基于ISO 8601格式的日期item.sunrise,分别提取日期和时间维度绘制图表,但编译器报错:
Initializer init(x:y:) requires that DateComponents conform to Plottable
相关代码如下:
Chart{ ForEach(sunriseSunsetDates) {item in LineMark( x: .value("Date", calendar.dateComponents([.year, .month, .day], from: item.sunrise)), y: .value("Time", calendar.dateComponents([.hour, .minute, .second], from: item.sunrise)) ) .foregroundStyle(.blue.gradient) .interpolationMethod(.catmullRom) } }
请问该需求能否实现,还是需要改用其他写法?
解决方案
这个需求完全可以实现,问题核心是DateComponents并没有遵循SwiftUI Charts要求的Plottable协议,因此不能直接作为坐标轴值传入LineMark。你可以通过两种常见方式修复:
方式一:将日期/时间转换为Date类型
把提取的日期、时间维度重新封装为Date实例(Date天然支持Plottable),同时通过坐标轴格式化控制显示样式:
let calendar = Calendar.current Chart{ ForEach(sunriseSunsetDates) { item in // 提取日期部分,转为仅包含年月日的Date guard let dateOnly = calendar.date(from: calendar.dateComponents([.year, .month, .day], from: item.sunrise)) else { return } // 提取时间部分,基于一个固定基准日期生成仅包含时分秒的Date let timeComponents = calendar.dateComponents([.hour, .minute, .second], from: item.sunrise) guard let timeOnly = calendar.date(bySettingHour: timeComponents.hour!, minute: timeComponents.minute!, second: timeComponents.second!, of: Date.distantPast) else { return } LineMark( x: .value("Date", dateOnly), y: .value("Time", timeOnly) ) .foregroundStyle(.blue.gradient) .interpolationMethod(.catmullRom) } } // 自定义X轴显示格式:仅展示年月日 .chartXAxis { AxisMarks(position: .bottom) { _ in AxisGridLine() AxisTick() AxisLabel(format: .dateTime.day().month().year()) } } // 自定义Y轴显示格式:仅展示时分 .chartYAxis { AxisMarks(position: .leading) { _ in AxisGridLine() AxisTick() AxisLabel(format: .dateTime.hour().minute()) } }
方式二:将时间转换为数值类型(秒数)
如果不需要Y轴以时间格式展示,也可以把时间转换为当天0点到日出时刻的秒数(数值类型天然支持Plottable),实现起来更简洁:
let calendar = Calendar.current Chart{ ForEach(sunriseSunsetDates) { item in guard let dateOnly = calendar.date(from: calendar.dateComponents([.year, .month, .day], from: item.sunrise)) else { return } // 计算当天0点的时间 let startOfDay = calendar.startOfDay(for: item.sunrise) // 计算日出时刻与当天0点的时间差(秒) let timeInSeconds = item.sunrise.timeIntervalSince(startOfDay) LineMark( x: .value("Date", dateOnly), y: .value("Time (Seconds)", timeInSeconds) ) .foregroundStyle(.blue.gradient) .interpolationMethod(.catmullRom) } }
内容的提问来源于stack exchange,提问作者Edward Hasted
相关产品推荐
相关产品推荐

