在 SwiftUI 中組合矩形來創(chuàng)建條形圖是比較容易的。SwiftUI 是一個很好的平臺,用于創(chuàng)建視圖和快速重構(gòu)獨(dú)立的子視圖。在 SwiftUI 中構(gòu)建條形圖需要做一些工作,隨著使用數(shù)據(jù)來試用條形圖,可以確定更多的定制化。
?前言
條形圖以矩形條的形式呈現(xiàn)數(shù)據(jù)的類別,其寬度和高度與它們表示的值成比例。本文將展示如何創(chuàng)建一個垂直條形圖,其中矩形的高度將代表每個類別的值。
開始圖表布局
SwiftUI 對探索不同布局和預(yù)覽實(shí)時視圖結(jié)果是很友好的。很容易將部分內(nèi)容提取到子視圖中,以便每個部分都很小且易于維護(hù)。從將包含 BarChartView? 以及可能的其他文本或數(shù)據(jù)的視圖開始。這個 BarChartView 包含一個標(biāo)題和一個圖表區(qū),它們由文本和圓角矩形表示。
struct ChartView1: View {
var body: some View {
VStack {
Text("Sample Bar Chart")
.font(.title)
BarChartView(
title: "the chart title")
.frame(width: 300, height: 300, alignment: .center)
Spacer()
}
}
}
struct BarChartView: View {
var title: String
var body: some View {
GeometryReader { gr in
let headHeight = gr.size.height * 0.10
VStack {
ChartHeaderView(title: title, height: headHeight)
ChartAreaView()
}
}
}
}
struct ChartHeaderView: View {
var title: String
var height: CGFloat
var body: some View {
Text(title)
.frame(height: height)
}
}
struct ChartAreaView: View {
var body: some View {
ZStack {
RoundedRectangle(cornerRadius: 5.0)
.fill(Color(#colorLiteral(red: 0.8906477705, green: 0.9005050659, blue: 0.8208766097, alpha: 1)))
}
}
}

圖表區(qū)添加條形圖
定義一些簡單的數(shù)據(jù)類別,例如一周內(nèi)每天的步數(shù)。以下列表數(shù)據(jù)被作為主視圖的項目數(shù)據(jù),每一條數(shù)據(jù)包含一個對(名稱,值)。在真正的 app 里,這里的數(shù)據(jù)應(yīng)該通過 ViewModel 從 model 里取數(shù)據(jù)。
每日步數(shù)數(shù)據(jù)
Day
| Steps
|
Mon
| 898
|
Tue
| 670
|
Wed
| 725
|
Thu
| 439
|
Fri
| 1232
|
Sat
| 771
|
Sun
| 365
|
struct DataItem: Identifiable {
let name: String
let value: Double
let id = UUID()
}
struct ChartView2: View {
let chartData: [DataItem] = [
DataItem(name: "Mon", value: 898),
DataItem(name: "Tue", value: 670),
DataItem(name: "Wed", value: 725),
DataItem(name: "Thu", value: 439),
DataItem(name: "Fri", value: 1232),
DataItem(name: "Sat", value: 771),
DataItem(name: "Sun", value: 365)
]
var body: some View {
VStack {
Text("Sample Bar Chart")
.font(.title)
BarChartView(
title: "Daily step count", data: chartData)
.frame(width: 350, height: 500, alignment: .center)
Spacer()
}
}
}
更新 BarChartView? 使數(shù)據(jù)可以作為參數(shù)傳遞到 ChartAreaView
struct BarChartView: View {
var title: String
var data: [DataItem]
var body: some View {
GeometryReader { gr in
let headHeight = gr.size.height * 0.10
VStack {
ChartHeaderView(title: title, height: headHeight)
ChartAreaView(data: data)
}
}
}
}
更新后的 BarChartView? 需要一個 DataItem? 的列表。GeometryReader 被用來確定條形圖的可用高度。數(shù)據(jù)中的最大值得到后并傳遞給每個 BarView?。主圖表區(qū)域保持原來的圓角矩形,并以水平堆疊的方式疊加一系列條形,每個 DataItem 一個。
struct ChartAreaView: View {
var data: [DataItem]
var body: some View {
GeometryReader { gr in
let fullBarHeight = gr.size.height * 0.90
let maxValue = data.map { $0.value }.max()!
ZStack {
RoundedRectangle(cornerRadius: 5.0)
.fill(Color(#colorLiteral(red: 0.8906477705, green: 0.9005050659, blue: 0.8208766097, alpha: 1)))
VStack {
HStack(spacing:0) {
ForEach(data) { item in
BarView(
name: item.name,
value: item.value,
maxValue: maxValue,
fullBarHeight: Double(fullBarHeight))
}
}
.padding(4)
}
}
}
}
}
為 BarView 創(chuàng)建一個新的試圖,該視圖為每條數(shù)據(jù)創(chuàng)建一個條形圖。它需要每一條數(shù)據(jù)的名稱和值以及最大值和可用的條形高度。每個條形圖都表示為圓角矩形,條形高度相對于最大條形高度設(shè)置。條形的顏色設(shè)置為純藍(lán)色。
struct BarView: View {
var name: String
var value: Double
var maxValue: Double
var fullBarHeight: Double
var body: some View {
let barHeight = (Double(fullBarHeight) / maxValue) * value
VStack {
Spacer()
ZStack {
VStack {
Spacer()
RoundedRectangle(cornerRadius:5.0)
.fill(Color.blue)
.frame(height: CGFloat(barHeight), alignment: .trailing)
}
VStack {
Spacer()
Text("\(value, specifier: "%.0F")")
.font(.footnote)
.foregroundColor(.white)
.fontWeight(.bold)
}
}
Text(name)
}
.padding(.horizontal, 4)
}
}

屏幕旋轉(zhuǎn)
條形圖在使用樣本數(shù)據(jù)時看起來不錯。圖表會調(diào)整到適合它所處的容器視圖之中。同樣的圖表可以放到任何沒有其他視圖的新試圖上,當(dāng)設(shè)備旋轉(zhuǎn)時,圖標(biāo)將會充滿空間并調(diào)整大小。
struct ChartView3: View {
var body: some View {
VStack() {
BarChartView(
title: "Daily step count", data: chartData)
Spacer()
}
.padding()
}
}

手機(jī)旋轉(zhuǎn)時顯示的圖表
真實(shí)數(shù)據(jù)的條形圖
給條形圖使用真實(shí)世界的數(shù)據(jù)。聯(lián)合國兒童基金會數(shù)據(jù)集中五歲以下兒童死亡率最高的十個國家。
五歲以下兒童死亡率:
指從出生到五歲之間死亡的概率,每1000名活產(chǎn)嬰兒
2019年特定國家五歲以下兒童死亡率估計數(shù)
ISO Code
| Country Name
| 2019
|
NGA
| Nigeria
| 117.2
|
SOM
| Somalia
| 116.9
|
TCD
| Chad
| 113.7
|
CAF
| Central African Republic
| 110.0
|
SLE
| Sierra Leone
| 109.2
|
GIN
| Guinea
| 98.8
|
SSD
| South Sudan
| 96.2
|
MLI
| Mali
| 94.0
|
BEN
| Benin
| 90.2
|
BFA
| Burkina Faso
| 87.5
|
LSO
| Lesotho
| 86.4
|
可以看出,國家名稱比示例數(shù)據(jù)中一周中的幾天使用多個數(shù)據(jù)名稱要長的多。數(shù)據(jù)使用國家名稱在條形圖中繪制。
struct ChartView4: View {
let chartData: [DataItem] = [
DataItem(name: "Nigeria", value: 117.2),
DataItem(name: "Somalia", value: 116.9),
DataItem(name: "Chad", value: 113.7),
DataItem(name: "Central African Republic", value: 110.0),
DataItem(name: "Sierra Leone", value: 109.2),
DataItem(name: "Guinea", value: 98.8),
DataItem(name: "South Sudan", value: 96.2),
DataItem(name: "Mali", value: 94.0),
DataItem(name: "Benin", value: 90.2),
DataItem(name: "Burkina Faso", value: 87.5)
]
var body: some View {
VStack() {
BarChartView(
title: "Under Five Mortality Rates in 2019", data: chartData)
.frame(width: 350, height: 500, alignment: .center)
Text("Under-five mortality rate:")
Text("is the probability of dying between birth and exactly 5 years of age, expressed per 1,000 live births.")
Spacer()
}
.padding()
}
}
這里對 BarView 做出了一些改動。條形圖上的值使用疊加視圖修改移到了條形圖的頂部。這個值是偏移的,所以文本不會離條形圖的頂部太近。數(shù)據(jù)名稱的字體大小和字重也可以被設(shè)置。向國家名稱那樣較長的文本,顯示出條形圖下面的文本將條形圖推到了線外。文本視圖的寬度被限制在條形圖寬度的范圍內(nèi),而且條形圖的標(biāo)簽文本會被截斷,條形圖的文本視圖也被限制在條形寬度的范圍內(nèi),并且文本可以被隱藏起來。
struct BarView: View {
var name: String
var value: Double
var maxValue: Double
var fullBarHeight: Double
var body: some View {
GeometryReader { gr in
let barHeight = (Double(fullBarHeight) / maxValue) * value
let textWidth = gr.size.width * 0.80
VStack {
Spacer()
RoundedRectangle(cornerRadius:5.0)
.fill(Color.blue)
.frame(height: CGFloat(barHeight), alignment: .trailing)
.overlay(
Text("\(value, specifier: "%.0F")")
.font(.footnote)
.foregroundColor(.white)
.fontWeight(.bold)
.frame(width: textWidth)
.offset(y:10)
,
alignment: .top
)
Text(name)
.font(.system(size: 11))
.fontWeight(.semibold)
.lineLimit(1)
.frame(width: textWidth)
}
.padding(.horizontal, 4)
}
}
}

所有的國家名稱都被截斷了,所以將數(shù)據(jù)更代為使用國家碼而不是國家名稱。圖標(biāo)被設(shè)置為固定大小,視圖被嵌入到 ScrollView 中,以便在設(shè)備旋轉(zhuǎn)時滾動。
struct ChartView5: View {
let chartData: [DataItem] = [
DataItem(name: "NGA", value: 117.2),
DataItem(name: "SOM", value: 116.9),
DataItem(name: "TCD", value: 113.7),
DataItem(name: "CAF", value: 110.0),
DataItem(name: "SLE", value: 109.2),
DataItem(name: "GIN", value: 98.8),
DataItem(name: "SSD", value: 96.2),
DataItem(name: "MLI", value: 94.0),
DataItem(name: "BEN", value: 90.2),
DataItem(name: "BFA", value: 87.5)
]
var body: some View {
ScrollView {
VStack() {
BarChartView(
title: "Countries with the highest Under Five Mortality Rates in 2019", data: chartData)
.frame(width: 350, height: 500, alignment: .center)
Spacer().frame(height:20)
VStack() {
Text("Under-five mortality rate:")
.font(.system(.title2, design:.rounded))
.fontWeight(.bold)
Text("is the probability of dying between birth and exactly 5 years of age, expressed per 1,000 live births.")
.font(.body)
}
.frame(width: 300, height: 130)
.background(Color(#colorLiteral(red: 0.8906477705, green: 0.9005050659, blue: 0.8208766097, alpha: 1)))
.cornerRadius(10)
Spacer()
}
.padding()
}
}
}

結(jié)語
在 SwiftUI 中組合矩形來創(chuàng)建條形圖是比較容易的。SwiftUI 是一個很好的平臺,用于創(chuàng)建視圖和快速重構(gòu)獨(dú)立的子視圖。在 SwiftUI 中構(gòu)建條形圖需要做一些工作,隨著使用數(shù)據(jù)來試用條形圖,可以確定更多的定制化。使用 GeometryReader 可以創(chuàng)建適應(yīng)更多可用環(huán)境的條形圖。在這篇文章中,我們創(chuàng)建了一個簡單的條形圖,有數(shù)值,下面有標(biāo)簽,還有圖表的標(biāo)題,下一步就是分離出 x 軸和 y 軸。