###### tags: `第14屆IT邦鐵人賽文章`
# 【在 iOS 開發路上的大小事2-Day28】來自 Apple 爸爸的最新力作 - Swift Charts 之 RuleMark 實作篇
上一篇介紹了 RectangleMark 的實作,今天要來介紹的是 Swift Charts 的 RuleMark
RuleMark 一共提供了六種 init 的方法,讓開發者可以繪製不同樣式的圖表
```swift
public init<Y>(xStart: CGFloat? = nil,
xEnd: CGFloat? = nil,
y: PlottableValue<Y>) where Y : Plottable
public init<X, Y>(xStart: PlottableValue<X>,
xEnd: PlottableValue<X>,
y: PlottableValue<Y>) where X : Plottable, Y : Plottable
public init<X>(xStart: PlottableValue<X>,
xEnd: PlottableValue<X>,
y: CGFloat? = nil) where X : Plottable
public init<X>(x: PlottableValue<X>,
yStart: CGFloat? = nil,
yEnd: CGFloat? = nil) where X : Plottable
public init<X, Y>(x: PlottableValue<X>,
yStart: PlottableValue<Y>,
yEnd: PlottableValue<Y>) where X : Plottable, Y : Plottable
public init<Y>(x: CGFloat? = nil,
yStart: PlottableValue<Y>,
yEnd: PlottableValue<Y>) where Y : Plottable
```
看了一下 Apple 官方範例後,覺得可以用 RuleMark 來繪製甘特圖
因此下面就用 RuleMark 來繪製甘特圖吧
## Model
```swift
import SwiftUI
struct EventEntity: Identifiable {
var id = UUID().uuidString
var title: String
var startDate: Date
var endDate: Date
init(year: Int, startMonth: Int, startDay: Int, numMonths: Int, title: String) {
self.title = title
let calendar = Calendar.autoupdatingCurrent
self.startDate = calendar.date(from: DateComponents(year: year, month: startMonth, day: startDay))!
self.endDate = calendar.date(byAdding: .month, value: numMonths, to: startDate)!
}
}
```
## ViewModel
```swift
import SwiftUI
class EventEntityViewModel {
var eventData: [EventEntity] = [
.init(year: 2022, startMonth: 1, startDay: 1, numMonths: 2, title: "Development"),
.init(year: 2022, startMonth: 3, startDay: 1, numMonths: 2, title: "Testing"),
.init(year: 2022, startMonth: 5, startDay: 1, numMonths: 2, title: "Debug"),
.init(year: 2022, startMonth: 7, startDay: 1, numMonths: 0, title: "Release")
]
}
```
## View
這邊要記得 import Charts,因為我們要顯示 RuleMark 在畫面上
然後這邊宣告了一個 ViewModel 的變數 vm
並在前面加上 @State 修飾字,讓 SwiftUI 來幫我們管理 ViewModel 狀態
接著是 Charts 的語法,語法也是很簡單,像是下面這樣
```swift
@State private var vm = EventEntityViewModel()
// 1:vm.eventData,圖表的資料來源
Chart(vm.eventData) {
RuleMark(
xStart: .value("Start Date", $0.startDate), // 2:x 軸的起點
xEnd: .value("End Date", $0.endDate), // 3:x 軸的終點
y: .value("Event", $0.title) // 4:y 軸要顯示的資料
)
}
.frame(height: 300)
.padding()
```
或者你也可以透過 ForEach 來寫,只是就會要讓 Model 繼承 Identifiable
並宣告 UUID() 變數在 Model 裡面,像是這樣 var id = UUID().uuidString
```swift
@State private var vm = EventEntityViewModel()
Chart {
// 1:vm.eventData,圖表的資料來源
ForEach(vm.eventData) { event in
RuleMark(
xStart: .value("Start Date", event.startDate), // 2:x 軸的起點
xEnd: .value("End Date", event.endDate), // 3:x 軸的終點
y: .value("Event", event.title) // 4:y 軸要顯示的資料
)
}
}
.frame(height: 300)
.padding()
```
現在的圖,應該會長得像下面這樣
![RuleMark 畫甘特圖](https://i.imgur.com/Ae6BrHL.png)
RuleMark 還可以搭配下一篇要介紹的 BarMark 來一起繪製具有平均線的柱狀圖
## RuleMark + BarMark
### Model
```swift
import SwiftUI
struct DepartmentEntity: Identifiable {
var id = UUID().uuidString
var department: String
var profit: Double
}
```
### ViewModel
```swift
import SwiftUI
class DepartmentEntityViewModel {
var departmentData: [DepartmentEntity] = [
.init(department: "Production", profit: 15000),
.init(department: "Marketing", profit: 8000),
.init(department: "Finance", profit: 10000)
]
}
```
### View
```swift
@State private var vm = DepartmentEntityViewModel()
Chart {
ForEach(vm.departmentData) { department in
BarMark(
x: .value("Department", department.department),
y: .value("Profit", department.profit)
)
}
RuleMark(
y: .value("Break Even Threshold", 9000)
)
.foregroundStyle(.red)
}
.frame(height: 300)
.padding()
```
將 RuleMark 跟 BarMark 結合完的圖,應該會長得像下面這樣
![RuleMark + BarMark](https://i.imgur.com/3LXWDAU.png)
## 完整程式碼 (RuleMark)
```swift
import SwiftUI
import Charts
struct RuleMarkView: View {
@State private var vm = EventEntityViewModel()
var body: some View {
Chart {
ForEach(vm.eventData) { event in
RuleMark(
xStart: .value("Start Date", event.startDate),
xEnd: .value("End Date", event.endDate),
y: .value("Event", event.title)
)
}
}
.chartYAxisLabel("2022 Event", alignment: .center)
.frame(height: 300)
.padding()
}
}
struct RuleMarkView_Previews: PreviewProvider {
static var previews: some View {
RuleMarkView()
}
}
```
## 完整程式碼 (RuleMark + BarMark)
```swift
import SwiftUI
import Charts
struct RuleMark_BarMarkView: View {
@State private var vm = DepartmentEntityViewModel()
var body: some View {
Chart {
ForEach(vm.departmentData) { department in
BarMark(
x: .value("Department", department.department),
y: .value("Profit", department.profit)
)
}
RuleMark(
y: .value("Break Even Threshold", 9000)
)
.foregroundStyle(.red)
}
.frame(height: 300)
.padding()
}
}
struct RuleMark_BarMarkView_Previews: PreviewProvider {
static var previews: some View {
RuleMark_BarMarkView()
}
}
```
## 總結
這篇依照 Apple 官方教學簡單實作了 Swift Charts 中的 RuleMark
明天會來介紹 Swift Charts 系列中的最後一種圖表 BarMark,讓我們繼續看下去吧~
## 參考資料
> 1. [https://developer.apple.com/documentation/charts/rulemark](https://developer.apple.com/documentation/charts/rulemark)