
概述
从 iOS 16 开始,Apple 为小伙伴们带来了全新的 SwiftUI Charts 框架,让我们可以用极少的代码便捷地绘制折线图、柱状图、饼图以及各种图,可谓"画龙点睛,一步登天"。

本系列文章将带宝子们从无到有,逐步拆解示例 TodayInYearsSetbacksComparisonChart
的源代码,并着重揭示几个开发中的"鬼斧神工"与"隐藏套路"。
在本篇博文中,您将学到如下内容:
-
- 倾斜的秘密:用 Annotation 自定义 X 轴标签
- 2.1 自定义 x 轴标签
- 2.2 拯救者:Annotation 出马!
想用 Charts 为自己精妙绝伦的 App 如虎添翼吗?看这篇就对啦! 无需等待,让我们马上开始 Charts 大冒险吧! Let's go!!!;)
2. 倾斜的秘密:用 Annotation 自定义 X 轴标签
2.1 自定义 x 轴标签
对于前一篇文章的问题,我们可以通过自己绘制图表的 x 轴标签来解决:
swift
Chart {...}
.chartXAxis {
AxisMarks { value in
if let yearIdx = value.as(Int.self), yearIdx < sortedYears.count {
let year = sortedYears[yearIdx]
AxisValueLabel("\(year)")
AxisGridLine()
}
}
}
在上面的代码中,我们使用图表的 chartXAxis 修改器"毅然决然"的抛弃了默认 x 轴标签,换成了自己 Nice 的年份显示:

不过,上面的结果没有显示所有的年份,这不是我们想要的。幸运的是,这可以通过 AxisMarks 中值的另一种构造器来"勉强"解决:
swift
Chart {...}
.chartXAxis {
AxisMarks(values: .stride(by: 1)) { value in
if let yearIdx = value.as(Int.self), yearIdx < sortedYears.count {
let year = sortedYears[yearIdx]
AxisValueLabel("\(year)")
AxisGridLine()
}
}
}
为什么说是"勉强"解决呢?因为即使这样,Charts 也总是忽略绘制最后一个年份(2017年):

所以,我们又该何去何从呢?
2.2 拯救者:Annotation 出马!
幸运的是,Charts 提供了一个 annotation 修改器方法,我们可以用它来进一步定制与图表元素相关的显示信息:

要达到用 annotation 模拟图表 x 轴标签的目的,我们需要解决两个小麻烦:
- 图片元素过分拥挤可能导致标签显示重叠
- 如何避免 annotation 下方多余的 y 轴标签?
为了让小伙伴们更清楚问题之所在,我们先用 annotation 实现一个初级版本一窥究竟:
swift
Chart {
ForEach(sortedYears.indices, id: \.self) { yearIndex in
let year = sortedYears[yearIndex]
let sbCount = sbCountInYears[year]!
let isThisYear = TimeMachine.shared.now.isSameYear(year)
BarMark(x: .value("年份序号", yearIndex), y: .value("次数", sbCount))
.foregroundStyle(isThisYear ? .blue : .red)
.annotation(position: .bottom, alignment: .center, spacing: 10) {
Text(verbatim: "\(year)")
.foregroundStyle(.gray)
.font(.footnote)
}
}
}
.chartXAxis(.hidden)
在上面的代码中,我们使用 annotation 完全替代了原本"不称职"的 x 轴标签,值得注意的是:我们需要使用 chartXAxis(.hidden)
修改器来隐藏默认的 x 轴标签。
运行结果如下所示:

看到我们之前所说的那两个麻烦了吗?
在这里,我们可以通过两种技术来解决它们,分别是标签倾斜 和定制 y 轴标签:
swift
Chart {
ForEach(sortedYears.indices, id: \.self) { yearIndex in
let year = sortedYears[yearIndex]
let sbCount = sbCountInYears[year]!
let isThisYear = TimeMachine.shared.now.isSameYear(year)
BarMark(x: .value("年份序号", yearIndex), y: .value("次数", sbCount))
.foregroundStyle(isThisYear ? .blue : .red)
.annotation(position: .bottom, alignment: .center, spacing: 10) {
Text(verbatim: "\(year)")
.foregroundStyle(.gray)
.font(.footnote)
.rotationEffect(.degrees(45))
.offset(x: 15)
}
}
}
.chartXAxis(.hidden)
.chartYAxis {
AxisMarks { value in
if let y = value.as(Int.self), y >= 0 {
AxisValueLabel("\(y)")
AxisGridLine()
}
}
}
在上面的代码中,我们主要做了如下几件事:
- 使用
rotationEffect
和offset
修改器使 annotation 中的 Text 文本略微倾斜,让各个标签拉开空档; - 使用自定义 y 轴标签忽略 0 以下标签;
.annotation(position: .bottom)
:在柱子底部添加自定义视图;- 倾斜 45° :用
.rotationEffect
; .offset(x: 15)
:微调位置,防止与坐标轴重叠。
再次编译运行,现在效果是不是好多了呢?

如此"锦上添花、一举两得",既能显示完整年份,又不会压缩布局,何乐而不为呢?
在下一篇博文中,我们将继续劈风斩浪,让一个个图表布局上的"多事之秋"成为我们的"刀下冤魂",敬请期待吧!
总结
在本篇博文中,我们讨论了如何利用图表 Annotation 和定制标签绘制,来避免图表元素重叠和过滤不需要的轴标签。
感谢观赏,我们下一篇再会啦!8-)