SwiftUI Charts 入门:从零到一,笑谈“柱”状人生(二)

概述

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

本系列文章将带宝子们从无到有,逐步拆解示例 TodayInYearsSetbacksComparisonChart 的源代码,并着重揭示几个开发中的"鬼斧神工"与"隐藏套路"。

在本篇博文中,您将学到如下内容:

    1. 倾斜的秘密:用 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 轴标签的目的,我们需要解决两个小麻烦:

  1. 图片元素过分拥挤可能导致标签显示重叠
  2. 如何避免 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()
        }
    }
}

在上面的代码中,我们主要做了如下几件事:

  1. 使用 rotationEffectoffset 修改器使 annotation 中的 Text 文本略微倾斜,让各个标签拉开空档;
  2. 使用自定义 y 轴标签忽略 0 以下标签;
  • .annotation(position: .bottom):在柱子底部添加自定义视图;
  • 倾斜 45° :用 .rotationEffect
  • .offset(x: 15):微调位置,防止与坐标轴重叠。

再次编译运行,现在效果是不是好多了呢?

如此"锦上添花、一举两得",既能显示完整年份,又不会压缩布局,何乐而不为呢?

在下一篇博文中,我们将继续劈风斩浪,让一个个图表布局上的"多事之秋"成为我们的"刀下冤魂",敬请期待吧!

总结

在本篇博文中,我们讨论了如何利用图表 Annotation 和定制标签绘制,来避免图表元素重叠和过滤不需要的轴标签。

感谢观赏,我们下一篇再会啦!8-)

相关推荐
HarderCoder2 天前
我们真的需要 typealias 吗?——一次 Swift 抽象成本的深度剖析
swift
HarderCoder2 天前
ByAI-Swift 6 全览:一份面向实战开发者的新特性速查手册
swift
HarderCoder2 天前
Swift 中 let 与 var 的真正区别:不仅关乎“可变”与否
swift
HarderCoder2 天前
深入理解 Swift 6.2 并发:从默认隔离到@concurrent 的完整指南
swift
麦兜*3 天前
Swift + Xcode 开发环境搭建终极指南
开发语言·ios·swiftui·xcode·swift·苹果vision pro·swift5.6.3
大熊猫侯佩4 天前
「内力探查术」:用 Instruments 勘破 SwiftUI 卡顿迷局
swiftui·debug·xcode
HarderCoder4 天前
Swift Concurrency:彻底告别“线程思维”,拥抱 Task 的世界
swift
HarderCoder4 天前
深入理解 Swift 中的 async/await:告别回调地狱,拥抱结构化并发
swift
HarderCoder4 天前
深入理解 SwiftUI 的 ViewBuilder:从隐式语法到自定义容器
swiftui·swift
HarderCoder4 天前
在 async/throwing 场景下优雅地使用 Swift 的 defer 关键字
swift