オラフ⛄️ 2022-8-12 最終稿
序文
iOS開発者として、日々の開発の中で多かれ少なかれアイコン表示の必要性に遭遇する. アイコンのニーズを解決するときは、チャートを描画するgithubのオープンソースのチャート描画フレームワーク: Chartsを直接使用または参照することになると思います. CoreGraphics に基づく このフレームワークは、チャート開発のニーズのかなりの部分をサポートします。
2022 年には、Apple 独自のチャート フレームワークが登場します。
まったく新しいSwift Chartsフレームワークは、WWDC22 で開発者に会いました。これは、データを有益でカスタマイズ可能な視覚化に変換する強力で簡潔な SwiftUI フレームワークです。関連する 4 つのセッションは次のとおりです。
セッションを視聴することを強くお勧めします!! !
フレームワークの機能
上の図は Seesion での効果表示で、シンプルな棒グラフから複雑なベクター チャート、ヒート マップまで、Swift チャートの強力な描画機能を示しています。Swift チャートは簡潔なコードで実装できます。Swift Charts は、ローカリゼーション ローカリゼーションとアクセシビリティ アクセシビリティ機能をサポートしています。チャート修飾子を使用してデフォルトの動作をオーバーライドし、チャートをカスタマイズすることもできます。たとえば、グラフにアニメーションを追加することで、動的なエクスペリエンスを作成できます。
互換性
iOS 16 以降、iPadOS 16.0 以降、macOS 13.0 以降、watchOS 9.0 以降
マーク
-
バーマーク
縦棒グラフ、進行状況棒グラフなど、さまざまな種類の棒グラフを BarMark で作成できます。
-
ラインマーク
折れ線グラフは、カテゴリまたは日付の属性をプロット (通常は x 位置を使用) し、数値カテゴリをプロット (通常は y 位置を使用) して作成できます。
-
ポイントマーク
PointMark チャート コンテンツを使用して、さまざまなタイプのポイント チャートを作成できます。ポイント マーカーを使用して作成できる一般的なチャートは、2 つの数値データ属性間の関係を示す散布図です。
-
エリアマーク
可以使用 AreaMark 将数据可视化为单个区域形状。 可以使用 AreaMark 图表内容创建不同类型的面积图。 要创建简单的区域标记图表,我们通常会将日期或有序字符串属性绘制到 x 位置,将数字绘制到 y 位置。
-
RuleMark
可以使用 RuleMark 在图表中绘制水平或垂直规则,如平均线,阈值线等。
-
RectangleMark
可以使用RectangleMark将数据字段映射成矩形图。 可以使用RectangleMark创建热图图表或注释图表中的矩形区域。
以上就是Swift Charts所有的Mark,它们可以单独使用,也可以组合使用,从而支撑开发者按需求开发出所需要的图表。用Session中的图来总结这些Mark吧
框架实践
实现Swift Charts框架,首先需要对SwiftUI的语法有一定的了解,下面将挑选典型图表进行coding实践。
BarkMark
如果我们需要用一张图表来直观的呈现多个国家的某一项开支费用,那我们就可以直接用BarMark来构建一个简单的柱状图来实现。
struct BarMarkData: Identifiable {
let name: String
let count: Double
var id: String{name}
}
let defData: [BarMarkData] = [
BarMarkData(name: "China", count: 15000),
BarMarkData(name: "US", count: 30000),
BarMarkData(name: "UK", count: 2000),
BarMarkData(name: "Japan", count: 800),
BarMarkData(name: "France", count: 3000)
]
struct BarMarkChartView: View {
var body: some View {
Chart(data) {
BarMark(
x: .value("name", $0.name),
y: .value("count", $0.count)
)
}.frame(width: 360, height: 300)
}
}
struct BarMarkChartView_Previews: PreviewProvider {
static var previews: some View {
AreaMarkChartView()
}
}
复制代码
我们看呈现效果:
如果这项开支还需要跟另一项开支进行对比呈现,这该怎么实现呢?我们只需要将上面的代码略微改动,即可实现,一下只粘贴新增部分的代码:
let eduData: [BarMarkData] = [
BarMarkData(name: "China", count: 28000),
BarMarkData(name: "US", count: 35000),
BarMarkData(name: "UK", count: 6000),
BarMarkData(name: "Japan", count: 2000),
BarMarkData(name: "France", count: 5000)
]
let mergeData = [
(outlay: "def", data: defData),
(outlay: "edu", data: eduData)
]
struct AreaMarkChartView: View {
var body: some View {
Chart(mergeData, id: .outlay) { mergeData in
ForEach(defData) { datum in
ForEach(mergeData.data, id: .id) { element in
BarMark(
x: .value("name", element.name),
y: .value("count", element.count)
).position(by: .value("outlay", mergeData.outlay))
.foregroundStyle(by: .value("outlay", mergeData.outlay))
}
}
}.frame(width: 360, height: 300)
}
}
复制代码
我们再来看看呈现效果:
现在已经实现了几个国家两个费用支出的对比图。对比上面,在代码上除了新增数据源,在Charts实现环节,也新增了position和foregroundStyle两个扩展方法
-
.position
position可以创建分组,让图表沿水平轴按其“类型”标记具有相同“产品”的mark。
-
.foregroundStyle
foregroundStyle按照传参不同,可进行不同类型的展示设置,如下:
extension ChartContent {
/// Sets the foreground style for marks in this chart content.
///
/// - Parameter color: The color.
public func foregroundStyle<S>( _ style: S) -> some ChartContent where S : ShapeStyle
/// Encodes data as the foreground style for marks in this chart content.
///
/// - Parameter data: The data property or value.
public func foregroundStyle<D>(by value: PlottableValue<D>) -> some ChartContent where D : Plottable
}
复制代码
LineMark
如果我们需要展示公司统计的一年内营收数据的变化,并且对比往年的数据,那此时我们可以用到的最简单的图就是线性图了,当然我们也可以使用其他的类型的图表来表示。下面我们先用LineMark来进行实践
struct SalesSummary: Identifiable {
let month: String
let total: Int
var id: String {month}
}
let pastData: [SalesSummary] = [
.init(month: "Jan", total: 3388),
.init(month: "Feb", total: 4420),
.init(month: "Mar", total: 6120),
...
.init(month: "Dec", total: 11076)
]
let lineData: [SalesSummary] = [
.init(month: "Jan", total: 5566),
.init(month: "Feb", total: 4610),
.init(month: "Mar", total: 5533),
...
.init(month: "Dec", total: 12998)
]
let salesData = [
(quarter: "2022", data: currentData),
(quarter: "2021", data: pastData)
]
struct LineMarkChartView: View {
var body: some View {
Chart{
ForEach(salesData, id: .years) { salesData in
ForEach(salesData.data, id: .id) { element in
LineMark(x: .value("month", element.month),
y: .value("total", element.total))
}.foregroundStyle(by: .value("sales", salesData.years))
}
}.frame(width: 360, height: 300)
}
}
struct LineMarkChartView_Previews: PreviewProvider {
static var previews: some View {
LineMarkChartView()
}
}
复制代码
我们来看效果:
如果想给折线图增加一个数据点的标记符号,同时让折线变的圆润,那我们就需要实现以下两个属性
struct LineMarkChartView: View {
var body: some View {
Chart{
ForEach(salesData, id: .years) { salesData in
ForEach(salesData.data, id: .id) { element in
LineMark(x: .value("month", element.month),
y: .value("total", element.total))
.interpolationMethod(.catmullRom)
.symbol(by: .value("sales", salesData.years))
}.foregroundStyle(by: .value("sales", salesData.years))
}
}.frame(width: 360, height: 300)
}
}
复制代码
再看下效果:
此时我们可以看到数据节点都用原点标记出来,并且折线也变成了更加柔和的曲线,这就是 interpolationMethod 和 symbol 这两个属性发挥的作用。
-
interpolationMethod
返回使用给定插值绘制数据的图表内容,包含多个,这里不一一列举了,可以查看api,这个方法仅提供给Line 和 Area 两种Mark
-
symbol
symbol根据传参不同有三个不同的方法实现,来支持不同的特性功能
extension ChartContent {
/// 设置此图表内容中标记的绘图符号类型
/// - Parameter symbol: The symbol.
public func symbol<S>( _ symbol: S) -> some ChartContent where S : ChartSymbolShape
/// 将数据编码为此图表内容中标记的符号。如上面的例子
/// - Parameter data: The data property or value.
public func symbol<D>(by value: PlottableValue<D>) -> some ChartContent where D : Plottable
/// 返回以给定视图作为绘图符号的图表内容。
/// - Parameter symbol: The view to use as the plotting symbol.
public func symbol<V>(@ViewBuilder symbol: () -> V) -> some ChartContent where V : View
}
复制代码
AreaMark
上面的公司年度营收的折线图数据同样也可以用来实践AreaMark区域图,我们来看看具体的代码实现,会不会更复杂呢,还是同样简单
let areaData: [BarMarkData] = [
.init(month: "Jan", total: 5566),
.init(month: "Feb", total: 4610),
.init(month: "Mar", total: 5533),
...
.init(month: "Dec", total: 12998)
]
struct AreaMarkChartView: View {
var body: some View {
Chart(areaData) { element in
AreaMark(
x: .value("month", element.month),
y: .value("total", element.total)
).interpolationMethod(.catmullRom)
.foregroundStyle(.pink)
}.frame(width: 360, height: 300)
}
}
复制代码
同样我们先来看看效果:
同样,AreaMark也使用了interpolationMethod来增加了边缘的曲线,同时还使用foregroundStyle来设置区域的颜色。
更多支持
Swift Charts可以三种数据类型作为他的数据值
-
Quantitative(数据型,如果Int、Double等)
-
Nominal(名义型,如各类名称标签)
-
Temporal(时间型,如年月周日时分等)
所以,6种Mark实际只有三种数据类别,每种Mark通常使用的属性通产个包括以下这些,这些属性,我们再上面的实践Demo中也都有使用到:
那么这些属性是不是足够了呢?
当然,是不够的。我们在实际开发中呈现的UI需求往往是定制化元素比较多的,现在我们来看看Swift Charts 框架都能够支持哪些内容的自定义,Seesion中给我们罗列了Chart的自定义内容维度:
接下来,我从途中所列的自定义属性入手,结合上面的的Demo例子,进一步实践Swift Charts的自定义能力。
增加Plot属性后,我们看看实际的效果
.chartPlotStyle { plot in
plot.background(.purple.opacity(0.8))
.border(.purple, width: 2)
.frame(width: 150, height: 100)
}
// 此时的效果,下图一。
// 如果将这个图标展示在Apple Watch,或者Widget上,
// 坐标线的效果此时会成为一种干扰,当然我们可以隐藏坐标线
.chartXAxis(.hidden)
.chartYAxis(.hidden)
// 此时的效果,下图二
复制代码
当然,关于这三个属性的应用,不止于此。它们都有很强大的自定义支撑能力来应对图标的展示需求,再结合Descriptions、Interaction、Color来实现更加酷炫的图表呈现。
关于使用
Charts的图表呈现能力虽然强大,但是我们都知道图表有它特殊的呈现场景,那么何时应该较交互呈现为图表,苹果的Session中已经给我们进行的设计指引。
当我们需要将数据进行变化、对比、进度(比例)呈现时,我们就可以使用Chart来帮我们进行可视化的实现 了。
如上三张图所示:
-
当我们需要可视化某项持续性数据变化趋势时,我们就可以使用Change部分的可视化方式来呈现。例如:用户单日步数、用户消费、浏览时长等数据变化趋势。
-
当我们需要可视化某项任务的进度,完成度、参与度、占比等等数据时,我们就可以使用Proportion部分的可视化形式来呈现。例如:用户年度App各类业务消费占比。
-
当我们需要可视化更全面数据的对比及跟维度的数据时,我们可以使用Comparison部分的可视化形式来呈现。如各公司的数据平台等系统的数据。
综上,Charts的使用场景,可能依然是众多App中的小部分,但是在特定行业App的业务中则是核心能力的展示,如你需要做一个股票或者财经类的App,又或者关于健康数据的App。Apple Watch的应用中则对Swift Charts有更多的需求。下面我们可以看看Session中的一些案例。
写在最后
Swift Charts是在WWDC22上和大家见面的,其强大的图表呈现能力,随着你对框架的挖掘和熟悉,会逐渐感知到。那么当你看了这篇文章后,你对Swift Charts有什么想法?
-
好家伙,兼容性最低iOS16,哪年那月才能用上...
-
SwiftUI に基づいて、danielgindi / Chartsを使用することもできます。結局のところ、SwiftUI には少なくとも iOS13 のサポートが必要です。SwiftUI が表示されるまでそれまで待ちます...
これらの問題は確かに存在し、Swift Charts に慣れている場合は、サポートできない要件シナリオがあることに気付くでしょう。Swift Charts は WWDC22 でお会いしましょう. WWDC23/24 以降でも、Swift Charts フレームワークは引き続き更新および改善されると思います. Widget Kit の開発が必要な場合、または Apple Watch の開発が必要な場合は、維持することをお勧めします.ご注意ください。