我們學會了 Linked List 知識後,接下來就是要結合 SwiftUI 知識一起實現這個資料結構了,如果你還沒懂鏈結串列,強烈建議你在前往上一篇複習 導讀 LeetCode 知識 - Linked List (Swift),這樣才能跟上這一篇的進度哦。
隨著越來越難的 LeetCode 知識,使用的 SwiftUI 元件也就越來越多元跟複雜,這次要實現鏈結串列,我們會使用到 Circle
、Path
、MVVM
。
首先建立 Linked List 資料結構程式碼,因為要用 ForEach
生成,所以需要一個獨立 ID,會用 UUID 來產生。
class ListNode<T>: Identifiable {
var id = UUID()
var value: T
var next: ListNode?
init(_ value: T, next: ListNode? = nil) {
self.value = value
self.next = next
}
}
再來要畫節點,我們用圓形 Circle
來表示一個節點,並且塗上藍色。
struct NodeView: View {
var node: ListNode<String>
var body: some View {
HStack {
Circle()
.fill(Color.blue)
.frame(width: 30, height: 30)
.overlay(
Text(node.value)
.foregroundColor(.white)
)
if let _ = node.next {
Arrow()
}
}
}
}
Arrow 就是我們判斷下一個節點不為空的時候產生的指針。
然後利用 Path
去畫黑色三角形指針指向下一個節點。
struct Arrow: View {
var body: some View {
Path { path in
path.move(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x: 10, y: 5))
path.addLine(to: CGPoint(x: 0, y: 10))
}
.fill(Color.black)
.frame(width: 10, height: 10)
}
}
畫出來如圖效果,我們成功建置了一個鏈結,跟一個黑色三角形指針。
struct LinkedListView: View {
var list: [ListNode<String>]
var body: some View {
HStack(spacing: 5) {
ForEach(list, id: \.id) { node in
NodeView(node: node)
}
}.frame(width: 500, height: 500)
}
}
最後利用 HStack
和 ForEach
去依據 ListNode 的陣列生成多個節點。
因為資料越來越多元複雜,我們利用 MVVM 架構拆分邏輯,使其程式碼更加乾淨,M 代表 Model, V 代表 View,這裡就是 SwiftUI,VM 則是 ViewModel 專門當 Model 跟 View 溝通的橋樑,讓產出資料的來源由 ViewModel 去控制 Model ,最後更新 View。
圖片來自維基百科。
所以產了一個 ViewModel ,這裡負責控制 Linked List,也就是傳進來的資料是 ListNode,然後我們把它轉換成 Array,之所以有這個轉換,是因為套用到 SwiftUI 的 ForEach
它只吃集合的資料,也就是這邊套用 Array 的話 UI 才能夠接受顯示。(筆者在這裡查了好久的資料,一直以為 ForEach
跟 for loop 運算符號沒什麼兩樣但其實能放的資料格式還是不太一樣)
class NumberViewModel: ObservableObject {
@Published var resList: [ListNode<String>] = []
func getLinkedList(nodes: ListNode<String>) {
var cur: ListNode? = nodes
var res = [ListNode<String>]()
while(cur != nil){
if let cur = cur {
res.append(cur)
}
cur = cur?.next
}
resList = res
}
}
要注意的是特別用了 @Published
還有 ObservableObject
是為了要告訴 SwiftUI 這是可變化的變數物件,當更動資料時就要去通知 SwiftUI 讓它更新跟這個資料有關聯的 View
的 body。
最後的主程式畫面程式碼如下,我們利用點擊後,會去創立一個新的鏈結串列。
struct ContentView: View {
@StateObject var viewModel = NumberViewModel()
var nodes = ListNode("1", next: ListNode("2", next: ListNode("3")))
var body: some View {
VStack{
LinkedListView(list: viewModel.resList)
}
Button("Add Linked List"){
viewModel.getLinkedList(nodes: nodes)
}
}
}
@ObservedObject
和 @StateObject
這裡使用的是 @StateObject
,在學習的過程中會發現 SwiftUI 有很多這種標記,之前用最多的是 @State
而這個主要是用在 Struct 的資料結構上的變數,設置他表示參數可以變動,但是資料一多的時候,會 Class 把多個資料包起來,這時候就會使用到 @StateObject
,它能保證物件單次建立,而 @ObservedObject
則會多次物件重新建立,在這個場景我們希望不要反覆建立物件,所以使用 @StateObject
。
最終效果如圖,只要按了 Add Linked List
按鈕就會使用 ViewModel 的運算,最後,我們成功在 SwiftUI 上建立了鏈結串列。
時間過得很快,我們已經學習 SwiftUI 跟 LeetCode 知識超過三十天的一半天數了,隨著日子一天一天過去,我們逐漸能夠把這些內容組建成一個 App,越來越有成就感,接下來會越來越進階,在撰寫文章的時候,筆者已經感受到越來越有挑戰了,加油,如果看到這裡有任何疑問也歡迎一起討論成長。