你不再需要建立自己的圖表庫或依賴第三方庫來建立圖表。 SwiftUI 框架現在附帶圖表 API。 借助 iOS 16 或更高版本中提供的這個圖錶框架,只需幾行程式碼即可呈現動畫圖表。
簡單來說,我們只需要定義 Mark,就可以構建出 SwiftUI 圖表。讓我們看看這個簡單的例子:
import SwiftUI
import Charts
struct ContentView: View {
var body: some View {
Chart {
BarMark(
x: .value("Day", "Monday"),
y: .value("Steps", 6019)
)
BarMark(
x: .value("Day", "Tuesday"),
y: .value("Steps", 7200)
)
}
}
}
無論我們想要構建長條圖還是折線圖,我們都會從 Chart
視圖開始。在圖表裡面,我們可以定義 bar mark,來提供圖表資料。BarMark
視圖是用來構建長條圖的,每一個 BarMark
視圖都會有 x
和 y
值,x
值就是代表 x 軸的圖表資料,如此類推。在以上的程式碼中,我把 x
軸的標籤設置為 Day,而 y
軸就是總步數。
讓我們在 Xcode 輸入以上程式碼,預覽就會自動顯示有兩個垂直長方體的長條圖。
以上就是創建長條圖最簡單的方法。不過,我們通常都不會對圖表數據進行硬編碼 (hardcode),而是在 Charts API 編寫一組數據。讓我們看看以下例子:
struct ContentView: View {
let weekdays = Calendar.current.shortWeekdaySymbols
let steps = [ 10531, 6019, 7200, 8311, 7403, 6503, 9230 ]
var body: some View {
Chart {
ForEach(weekdays.indices, id: \.self) { index in
BarMark(x: .value("Day", weekdays[index]), y: .value("Steps", steps[index]))
}
}
}
}
我們為圖表數據創建了兩個陣列(weekdays
和steps
)。 在 Chart
視圖中,我們讀取 weekdays
陣列並顯示圖表數據。 如果你已在 Xcode 項目中輸入程式碼,預覽部分應該呈現如圖 38.2 所示的條形圖。
在預設情況下,Charts API 會以相同顏色呈現所有長方體。如果我們想把每個長方體設置為不同的顏色,可以將 foregroundStyle
修飾符附加到 BarMark
視圖:
.foregroundStyle(by: .value("Day", weekdays[index]))
如果我們想為所有長方體添加註釋,可以使用 annotation
修飾器:
.annotation {
Text("\(steps[index])")
}
作出這些改動後,長條圖就更加漂亮了。
如果想要建立橫向的長條圖,我們只需要把 BarMark
視圖內的 x
和 y
參數 (parameter) 交換就可以了。
你已學會如何建立長條圖,現在我們會示範使用 SwiftUI Charts API,來構建一個折線圖,顯示 2021 年 7 月至 2022 年 6 月香港、台北和倫敦的平均氣溫。
讓我們先建立一個 WeatherData
結構,來儲存天氣數據。在你的 Xcode 項目中,使用 Swift File 模板創建一個名為 WeatherData
的新文件。 在文件中加入以下程式碼:
struct WeatherData: Identifiable {
let id = UUID()
let date: Date
let temperature: Double
init(year: Int, month: Int, day: Int, temperature: Double) {
self.date = Calendar.current.date(from: .init(year: year, month: month, day: day)) ?? Date()
self.temperature = temperature
}
}
let hkWeatherData = [
WeatherData(year: 2021, month: 7, day: 1, temperature: 30.0),
WeatherData(year: 2021, month: 8, day: 1, temperature: 29.0),
WeatherData(year: 2021, month: 9, day: 1, temperature: 30.0),
WeatherData(year: 2021, month: 10, day: 1, temperature: 26.0),
WeatherData(year: 2021, month: 11, day: 1, temperature: 23.0),
WeatherData(year: 2021, month: 12, day: 1, temperature: 19.0),
WeatherData(year: 2022, month: 1, day: 1, temperature: 18.0),
WeatherData(year: 2022, month: 2, day: 1, temperature: 15.0),
WeatherData(year: 2022, month: 3, day: 1, temperature: 22.0),
WeatherData(year: 2022, month: 4, day: 1, temperature: 24.0),
WeatherData(year: 2022, month: 5, day: 1, temperature: 26.0),
WeatherData(year: 2022, month: 6, day: 1, temperature: 29.0)
]
let londonWeatherData = [
WeatherData(year: 2021, month: 7, day: 1, temperature: 19.0),
WeatherData(year: 2021, month: 8, day: 1, temperature: 17.0),
WeatherData(year: 2021, month: 9, day: 1, temperature: 17.0),
WeatherData(year: 2021, month: 10, day: 1, temperature: 13.0),
WeatherData(year: 2021, month: 11, day: 1, temperature: 8.0),
WeatherData(year: 2021, month: 12, day: 1, temperature: 8.0),
WeatherData(year: 2022, month: 1, day: 1, temperature: 5.0),
WeatherData(year: 2022, month: 2, day: 1, temperature: 8.0),
WeatherData(year: 2022, month: 3, day: 1, temperature: 9.0),
WeatherData(year: 2022, month: 4, day: 1, temperature: 11.0),
WeatherData(year: 2022, month: 5, day: 1, temperature: 15.0),
WeatherData(year: 2022, month: 6, day: 1, temperature: 18.0)
]
let taipeiWeatherData = [
WeatherData(year: 2021, month: 7, day: 1, temperature: 31.0),
WeatherData(year: 2021, month: 8, day: 1, temperature: 30.0),
WeatherData(year: 2021, month: 9, day: 1, temperature: 30.0),
WeatherData(year: 2021, month: 10, day: 1, temperature: 26.0),
WeatherData(year: 2021, month: 11, day: 1, temperature: 22.0),
WeatherData(year: 2021, month: 12, day: 1, temperature: 19.0),
WeatherData(year: 2022, month: 1, day: 1, temperature: 17.0),
WeatherData(year: 2022, month: 2, day: 1, temperature: 17.0),
WeatherData(year: 2022, month: 3, day: 1, temperature: 21.0),
WeatherData(year: 2022, month: 4, day: 1, temperature: 23.0),
WeatherData(year: 2022, month: 5, day: 1, temperature: 24.0),
WeatherData(year: 2022, month: 6, day: 1, temperature: 27.0)
]
因為 Chart initializer 接受 Identifiable
物件的列表,所以我們要讓 WeatherData
遵從 Identifiable
協定。我們要為每個城市創建一個陣列 (array),來儲存天氣數據。
在項目導航器中,使用 SwiftUI View 模板創建一個名為 SimpleLineChartView
的新文件。不論我們要利用 Charts 框架建立什麼圖表,都需要先匯入 Charts
框架:
import Charts
然後,我們宣告一個陣列去儲存三個城市的天氣數據:
let chartData = [ (city: "Hong Kong", data: hkWeatherData),
(city: "London", data: londonWeatherData),
(city: "Taipei", data: taipeiWeatherData) ]
在body
變數中,像這樣更新程式碼以創建折線圖:
VStack {
Chart {
ForEach(hkWeatherData) { item in
LineMark(
x: .value("Month", item.date),
y: .value("Temp", item.temperature)
)
}
}
.frame(height: 300)
}
以上的程式碼繪製了一個折線圖,來顯示香港的平均氣溫。ForEach
語句 loop through 儲存在 hkWeatherData
中的所有項目。我們會為每個項目創建一個 LineMark
物件,當中 x
軸設置為日期,而 y
軸則設置為平均氣溫。
我們也可以選擇使用 frame
修飾符,來調整圖表的大小。如果我們在 Xcode 預覽中預覽程式碼,應該會看到如圖 38.5 的折線圖:
我們可以利用 chartXAxis
和 chartYAxis
修飾符,來客製化 x 和 y 軸。比如說,如果我們想以數字格式顯示月份,我們可以將 chartXAxis
修飾符附加到 Chart
視圖:
.chartXAxis {
AxisMarks(values: .stride(by: .month)) { value in
AxisGridLine()
AxisValueLabel(format: .dateTime.month(.defaultDigits))
}
}
在 chartXAxis
中,我們為月份的數值創建了一個 AxisMarks
的視覺標記 (visual mark)。針對每個數值,我們可以使用特定格式顯示一個 ValueLabel。以下這行程式碼就告訴了 SwiftUI 圖表,我們想要使用數字格式顯示月份:
.dateTime.month(.defaultDigits)
另外,我們也使用了 AxisGridLine
來添加一些 grid line。
至於 y 軸,我們之前是在後面(右側)顯示 y 軸的,我們想改為在前面(左側)顯示。讓我們如此附加 chartYAxis
修飾符:
.chartYAxis {
AxisMarks(position: .leading)
}
做好改動之後,Xcode 預覽應該會把圖表更新如下。y 軸會在左側顯示,而月份的格式也會變成以數字格式顯示。另外,你也應該會看到 grid line。
我們可以利用 chartPlotStyle
修飾器,來更改繪圖區域的背景顏色。讓我們將修飾符附加到 Chart
視圖:
.chartPlotStyle { plotArea in
plotArea
.background(.blue.opacity(0.1))
}
然後,我們就可以使用 background
修飾符更改繪圖區域的顏色。在上面的例子中,我們把繪圖區域更改成淺藍色。
現在,圖表只顯示單一數據(香港的天氣數據),那我們如何把倫敦和台北的天氣數據顯示在同一個折線圖中呢?
我們可以這樣重寫 Chart
視圖的程式碼:
Chart {
ForEach(chartData, id: \.city) { series in
ForEach(series.data) { item in
LineMark(
x: .value("Month", item.date),
y: .value("Temp", item.temperature)
)
}
.foregroundStyle(by: .value("City", series.city))
}
}
我們有另一個 ForEach
來讀取三個城市的數據。我們在這裡使用了 foregroundStyle
修飾符,為每條線應用不同的顏色。我們不需要指定顏色,SwiftUI 會自動選擇顏色。
現在,三個城市的符號都相同。如果要使用不同的符號,讓我們在 foregroundStyle
之後添加這行程式碼:
.symbol(by: .value("City", series.city))
如此一來,不同城市就會有不同的符號了。
我們可以把 interpolationMethod
修飾器附加到 LineMark
,來更改折線圖的內插方法。
.interpolationMethod(.stepStart)
如果我們把內插方法設置為 .stepStart
,折線圖就會變成這樣:
除了 .stepStart
之外,我們還可以使用以下設定:
Charts 框架是 SwiftUI 一個很好的新功能,即使是 SwiftUI 的初學者,用幾行程式碼,就可以構建出漂亮的圖表。雖然這篇教學文章以折線圖為例子,但其實我們可以利用 Charts API 輕鬆地將折線圖轉換為其他圖表,例如長條圖。你可以參閱 Swift Charts 文檔深入了解這個 API。
在本章所準備的範例檔中,有最後完整的 Xcode 專案,可供你下載參考: