SwiftUI Chart使用日期时Y轴数据与刻度网格不对齐问题求助
SwiftUI折线图数据点与Y轴标签/网格线不对齐(偏下移)
我在SwiftUI中用Charts框架创建了折线图,图表能正常绘制,但所有数据点都无法和Y轴的标签、网格线对齐,会略微偏下。

目前只用基础代码实现,找不到排查方向,想知道这是SwiftUI的Bug吗?有没有人遇到过同样的问题?
完整代码如下:
import SwiftUI import Charts struct Time { let date: String let time: String } struct ChartsView: View { @State var timeData: [Time] = [ .init(date: "2023-12-01", time: "07:50:00"), .init(date: "2023-12-02", time: "08:00:00"), .init(date: "2023-12-03", time: "08:10:00"), .init(date: "2023-12-04", time: "08:00:00"), .init(date: "2023-12-05", time: "08:00:00"), .init(date: "2023-12-06", time: "07:50:00"), .init(date: "2023-12-07", time: "08:00:00"), .init(date: "2023-12-08", time: "07:50:00"), .init(date: "2023-12-09", time: "08:10:00"), .init(date: "2023-12-10", time: "08:10:00"), .init(date: "2023-12-11", time: "08:10:00"), .init(date: "2023-12-12", time: "08:10:00"), .init(date: "2023-12-13", time: "07:50:00"), .init(date: "2023-12-14", time: "08:10:00"), .init(date: "2023-12-15", time: "07:50:00"), .init(date: "2023-12-16", time: "07:50:00"), .init(date: "2023-12-17", time: "08:00:00"), .init(date: "2023-12-18", time: "08:00:00"), .init(date: "2023-12-19", time: "08:00:00"), .init(date: "2023-12-20", time: "08:00:00"), .init(date: "2023-12-21", time: "08:00:00"), .init(date: "2023-12-22", time: "08:10:00"), .init(date: "2023-12-23", time: "08:00:00"), .init(date: "2023-12-24", time: "08:00:00"), .init(date: "2023-12-25", time: "08:00:00"), .init(date: "2023-12-26", time: "07:50:00"), .init(date: "2023-12-27", time: "08:00:00"), .init(date: "2023-12-28", time: "08:00:00"), .init(date: "2023-12-29", time: "08:00:00"), .init(date: "2023-12-30", time: "08:10:00"), .init(date: "2023-12-31", time: "07:50:00") ] var body: some View { ZStack(alignment: .bottom, content: { Chart { ForEach(timeData, id: \.date) { LineMark(x: .value("x", $0.date.dateYYYYMMdd ?? Date(), unit: .day), y: .value("y", $0.time.dateHHmmss ?? Date(), unit: .minute)) .foregroundStyle(.red.opacity(0.3)) .lineStyle(StrokeStyle(lineWidth: 1)) .symbol { Circle() .fill(.red) .frame(width: 4) } RuleMark(y: .value("y", "08:08:00".dateHHmmss ?? Date(), unit: .minute)) } } .frame(height: 165) .padding(24) }) } } extension String { var dateYYYYMMdd: Date? { let formatter = DateFormatter() formatter.dateFormat = "YYYY-MM-dd" return formatter.date(from: self) } var dateHHmmss: Date? { let formatter = DateFormatter() formatter.dateFormat = "HH:mm:ss" return formatter.date(from: self) } }
问题原因与修复方案
这不是SwiftUI的Bug,核心问题出在时间字符串转Date时的默认日期偏差:
你的dateHHmmss扩展只转换时分秒,生成的Date会默认使用2000-01-01作为日期部分;而X轴的dateYYYYMMdd转换出来的是2023年12月的日期。Charts框架在计算Y轴位置时,会把整个Date对象(包含年月日时分秒)纳入计算,不同日期的同一天时间,因为时区、闰秒等因素,实际时间戳会有微小差异,导致数据点位置偏移。
另外,你在LineMark和RuleMark的y参数里加了unit: .minute,这个参数是用来截断日期单位而非仅提取时分秒,会进一步放大计算偏差。
修复步骤:
统一Date的基准日期:在转换时分秒时,把日期部分固定为和X轴一致的基准(比如用X轴对应的日期,或者统一用某一个固定日期)。
移除不必要的
unit参数:如果只需要比较时分秒,不需要截断单位,直接传递转换后的Date即可。
修改后的代码示例:
import SwiftUI import Charts struct Time { let date: String let time: String } struct ChartsView: View { @State var timeData: [Time] = [ // 保持原数据不变 .init(date: "2023-12-01", time: "07:50:00"), .init(date: "2023-12-02", time: "08:00:00"), .init(date: "2023-12-03", time: "08:10:00"), .init(date: "2023-12-04", time: "08:00:00"), .init(date: "2023-12-05", time: "08:00:00"), .init(date: "2023-12-06", time: "07:50:00"), .init(date: "2023-12-07", time: "08:00:00"), .init(date: "2023-12-08", time: "07:50:00"), .init(date: "2023-12-09", time: "08:10:00"), .init(date: "2023-12-10", time: "08:10:00"), .init(date: "2023-12-11", time: "08:10:00"), .init(date: "2023-12-12", time: "08:10:00"), .init(date: "2023-12-13", time: "07:50:00"), .init(date: "2023-12-14", time: "08:10:00"), .init(date: "2023-12-15", time: "07:50:00"), .init(date: "2023-12-16", time: "07:50:00"), .init(date: "2023-12-17", time: "08:00:00"), .init(date: "2023-12-18", time: "08:00:00"), .init(date: "2023-12-19", time: "08:00:00"), .init(date: "2023-12-20", time: "08:00:00"), .init(date: "2023-12-21", time: "08:00:00"), .init(date: "2023-12-22", time: "08:10:00"), .init(date: "2023-12-23", time: "08:00:00"), .init(date: "2023-12-24", time: "08:00:00"), .init(date: "2023-12-25", time: "08:00:00"), .init(date: "2023-12-26", time: "07:50:00"), .init(date: "2023-12-27", time: "08:00:00"), .init(date: "2023-12-28", time: "08:00:00"), .init(date: "2023-12-29", time: "08:00:00"), .init(date: "2023-12-30", time: "08:10:00"), .init(date: "2023-12-31", time: "07:50:00") ] var body: some View { ZStack(alignment: .bottom, content: { Chart { ForEach(timeData, id: \.date) { item in guard let xDate = item.date.dateYYYYMMdd, let yDate = item.time.dateHHmmss(with: xDate) else { return } LineMark(x: .value("x", xDate), y: .value("y", yDate)) .foregroundStyle(.red.opacity(0.3)) .lineStyle(StrokeStyle(lineWidth: 1)) .symbol { Circle() .fill(.red) .frame(width: 4) } } RuleMark(y: .value("基准线", "08:08:00".dateHHmmss() ?? Date())) .foregroundStyle(.gray.opacity(0.5)) } .frame(height: 165) .padding(24) }) } } extension String { var dateYYYYMMdd: Date? { let formatter = DateFormatter() formatter.dateFormat = "yyyy-MM-dd" // 替换为小写yyyy,避免周年计算偏差 return formatter.date(from: self) } func dateHHmmss(with baseDate: Date = Date(timeIntervalSince1970: 0)) -> Date? { let formatter = DateFormatter() formatter.dateFormat = "HH:mm:ss" guard let timeDate = formatter.date(from: self) else { return nil } // 提取时分秒,替换到基准日期上 let calendar = Calendar.current let components = calendar.dateComponents([.hour, .minute, .second], from: timeDate) return calendar.date(bySettingHour: components.hour, minute: components.minute, second: components.second, of: baseDate) } }
额外注意点:
- 日期格式化时,
YYYY是周年(基于周的年份),日常日期转换应该用yyyy(格里高利历年份),避免跨年时出现日期偏差。 - RuleMark不需要放在ForEach里,否则会为每个数据点绘制一条重复的线,移到外面只绘制一次即可。
内容的提问来源于stack exchange,提问作者Justin.Shim
相关产品推荐
相关产品推荐

