Swift Charts X轴样式优化:刻度标签间距及首尾主刻度设置问题
Swift Charts 折线轴样式问题解决方案
需求
使用Swift Charts创建股票时间价值折线图,需实现:
- 每月显示内置(inset)刻度
- 起始、末尾及中间的主刻度尺寸更大,并在下方添加日期标签
现存问题
- 底部轴网格线与值标签间距异常:设置
verticalSpacing: 0后,网格线与标签仍有约16pt间距,使用负数值虽能临时解决,但非合理方案。 - X轴首尾主刻度无法强制显示:当前
.automatic(desiredCount:)生成的主刻度无法确保首尾日期必带标签。
解决方案
问题1:修复标签与刻度间距
额外间距源于你将主刻度和标签拆分为两组AxisMarks,各自占用独立布局空间。正确做法是将刻度与标签放在**同一组AxisMarks**中:
// 替换原X轴的主刻度+标签代码 AxisMarks(preset: .inset, values: .automatic(desiredCount: numberOfMajorTicks)) { value in AxisTick(length: 16, stroke: StrokeStyle(lineWidth: 1)) .foregroundStyle(Color.black) AxisValueLabel(format: .dateTime.month().year(.twoDigits), verticalSpacing: 0) }
此时verticalSpacing: 0会直接作用于刻度与标签的间距,无需负数值调整。
问题2:强制首尾日期作为主刻度
.automatic(desiredCount:)无法保证首尾必选,需手动组合首尾日期与中间自动刻度:
.chartXAxis { let numberOfMajorTicks = 3 guard let startDate = fundValues.first?.date, let endDate = fundValues.last?.date else { return } // 生成中间的自动刻度(排除首尾,避免重复) let middleValues = AxisMarks.Position.automatic(desiredCount: numberOfMajorTicks - 2) .values(for: .value("Date", startDate), to: .value("Date", endDate)) .compactMap { $0.dateValue } // 合并首尾+中间刻度,确保首尾必选 let customMajorValues = [startDate] + middleValues + [endDate] // 主刻度与标签 AxisMarks(values: .init(customMajorValues)) { value in AxisTick(length: 16, stroke: StrokeStyle(lineWidth: 1)) .foregroundStyle(Color.black) AxisValueLabel(format: .dateTime.month().year(.twoDigits), verticalSpacing: 0) } // 次要刻度(每月),跳过主刻度位置避免重复 AxisMarks(preset: .inset, values: .stride(by: .month)) { value in guard let date = value.dateValue, !customMajorValues.contains(date) else { return } AxisTick(centered: true, length: 8, stroke: StrokeStyle(lineWidth: 1)) .foregroundStyle(Color.black) } }
修改后的完整代码
import SwiftUI import Charts struct PlanValue: Codable, Identifiable { let value: Double let date: Date var id: Date { date } } // Date formatter to parse the date strings let dateFormatter: ISO8601DateFormatter = { let formatter = ISO8601DateFormatter() formatter.formatOptions = [.withFullDate] return formatter }() let fundValues: [PlanValue] = [ PlanValue(value: 100.58, date: dateFormatter.date(from: "2023-01-30")!), PlanValue(value: 200.22, date: dateFormatter.date(from: "2023-01-31")!), PlanValue(value: 505.66, date: dateFormatter.date(from: "2023-02-28")!), PlanValue(value: 399.33, date: dateFormatter.date(from: "2023-03-31")!), PlanValue(value: 5652.37, date: dateFormatter.date(from: "2023-04-30")!), PlanValue(value: 5755.66, date: dateFormatter.date(from: "2023-05-31")!), PlanValue(value: 5850.81, date: dateFormatter.date(from: "2023-06-30")!), PlanValue(value: 6002.82, date: dateFormatter.date(from: "2023-07-31")!), PlanValue(value: 5750.62, date: dateFormatter.date(from: "2023-08-31")!), PlanValue(value: 5699.93, date: dateFormatter.date(from: "2023-09-30")!), PlanValue(value: 5820.01, date: dateFormatter.date(from: "2023-10-31")!), PlanValue(value: 6105.03, date: dateFormatter.date(from: "2023-11-30")!), PlanValue(value: 6291.02, date: dateFormatter.date(from: "2023-12-31")!), PlanValue(value: 6399.48, date: dateFormatter.date(from: "2024-01-31")!), PlanValue(value: 6544.99, date: dateFormatter.date(from: "2024-02-29")!), PlanValue(value: 6700.69, date: dateFormatter.date(from: "2024-03-31")!), PlanValue(value: 6850.57, date: dateFormatter.date(from: "2024-04-30")!), PlanValue(value: 6998.78, date: dateFormatter.date(from: "2024-05-31")!), PlanValue(value: 6400.39, date: dateFormatter.date(from: "2024-06-30")!), PlanValue(value: 6450.33, date: dateFormatter.date(from: "2024-07-31")!), PlanValue(value: 29555.39, date: dateFormatter.date(from: "2024-08-31")!), PlanValue(value: 30300.39, date: dateFormatter.date(from: "2024-09-30")!), PlanValue(value: 30836.33, date: dateFormatter.date(from: "2024-10-31")!), PlanValue(value: 30750.06, date: dateFormatter.date(from: "2024-11-30")!), PlanValue(value: 31011.97, date: dateFormatter.date(from: "2024-12-31")!), PlanValue(value: 32500.24, date: dateFormatter.date(from: "2025-01-29")!) ] struct ContentView: View { var body: some View { Chart { // Fund Values ForEach(fundValues) { planValue in LineMark(x: .value("Date", planValue.date), y: .value("Value", planValue.value) ) } } .chartXAxis { let numberOfMajorTicks = 3 guard let startDate = fundValues.first?.date, let endDate = fundValues.last?.date else { return } // 生成中间的自动刻度(排除首尾,避免重复) let middleValues = AxisMarks.Position.automatic(desiredCount: numberOfMajorTicks - 2) .values(for: .value("Date", startDate), to: .value("Date", endDate)) .compactMap { $0.dateValue } // 合并首尾+中间刻度,确保首尾必选 let customMajorValues = [startDate] + middleValues + [endDate] // 主刻度与标签 AxisMarks(values: .init(customMajorValues)) { value in AxisTick(length: 16, stroke: StrokeStyle(lineWidth: 1)) .foregroundStyle(Color.black) AxisValueLabel(format: .dateTime.month().year(.twoDigits), verticalSpacing: 0) } // 次要刻度(每月),跳过主刻度位置避免重复 AxisMarks(preset: .inset, values: .stride(by: .month)) { value in guard let date = value.dateValue, !customMajorValues.contains(date) else { return } AxisTick(centered: true, length: 8, stroke: StrokeStyle(lineWidth: 1)) .foregroundStyle(Color.black) } } .chartYAxis { AxisMarks(preset: .aligned, values: .automatic(desiredCount: 7) ) { value in let isFirstLine = value.index == 0 AxisGridLine(stroke: StrokeStyle(lineWidth: 1, dash: [isFirstLine ? 0 : 4], dashPhase: 0)) .foregroundStyle(isFirstLine ? .black : .gray) AxisValueLabel(format: .currency(code: "GBP").notation(.compactName)) } } .frame(height: 275) .padding() } } #Preview { ContentView() }
内容的提问来源于stack exchange,提问作者bencallis
相关产品推荐
相关产品推荐

