WWDC22- Swift Charts初探
Olaf⛄️ 2022-8-12終稿
前言
作為一個iOS開發者,我們在日常開發中,多多少少會遇到圖示展示類的需求,相信大家解決圖示需求時,都會直接使用或者參考github開源的圖表繪製框架:Charts,這個基於CoreGraphics繪製圖表的框架,支撐了相當一部分的圖表開發需求。
2022,Apple自己的圖表框架來了!
全新的 Swift Charts 框架在WWDC22中跟開發者見面了。這是一個強大而簡潔的SwiftUI框架,能將資料轉換為資訊豐富的可自定義視覺化圖表。下面是相關的4個Session:
強力建議觀看Session!!!
框架特性
上圖是Seesion中的效果呈現、展示了Swift Charts強大的繪製能力,從簡單的柱狀圖到複雜的向量圖,熱力圖,Swift Charts都可以用簡潔的程式碼實現。Swift Charts 支援 localization 本地化 和 accessibility 輔助功能。還可以使用圖表修飾符覆蓋預設行為以自定義圖表。 例如,可以通過向圖表新增動畫來建立動態體驗。
相容性
iOS 16+、iPadOS 16.0+、macOS 13.0+、watchOS 9.0+
Marks
- BarMark
可以使用BarMark建立不同型別的條形圖,如柱狀圖,進度條圖等。 * LineMark
可以通過繪製類別或日期屬性(通常使用 x 位置)和繪製數字類別(通常使用 y 位置)來建立折線圖。 * PointMark
可以使用 PointMark 圖表內容建立不同型別的點圖表。可以使用點標記構建的一個常見圖表是散點圖,它顯示兩個數值資料屬性之間的關係。 * AreaMark
可以使用 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() } } ```
我們看呈現效果:
如果這項開支還需要跟另一項開支進行對比呈現,這該怎麼實現呢?我們只需要將上面的程式碼略微改動,即可實現,一下只貼上新增部分的程式碼:
```Swift 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根據傳參不同有三個不同的方法實現,來支援不同的特性功能
```Swift
extension ChartContent {
/// 設定此圖表內容中標記的繪圖符號型別
/// - Parameter symbol: The symbol.
public func symbol( _ 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開發需求的小夥伴,建議持續保持關注。