iT邦幫忙

2023 iThome 鐵人賽

DAY 29
0
Mobile Development

用 SwiftUI 魔法變出 Leetcode 刷題知識學習 App!系列 第 29

Day 29: SwiftUI Search bar 搜尋 LeetCode 列表,Section 顯示演算法主題

  • 分享至 

  • xImage
  •  

本篇要來實踐 SwiftUI 列表搜尋功能,在 LeetCode 題目越來越多且越來越複雜的情況下,要一個一個找想看的題目變得越來越麻煩,此時就需要善用搜尋功能快速找到我們想看的題目。

搜尋功能介紹

首先 SwiftUI 很方便,提供我們加入搜尋的功能。

struct ContentView: View {
    @State private var searchText = ""
    
    var body: some View {
        NavigationStack {
                    Text("Searching for \(searchText)")
                        .navigationTitle("Searchable Example")
                }
                .searchable(text: $searchText)
    }
}

可以在 NavigationStack 建立 .searchable記錄使用者輸入的搜尋關鍵字,然後就有搜尋文字。searchText 這個狀態變數就是儲存輸入的搜尋關鍵字。

如圖,SwiftUI 給我們基本的搜尋框可以搜尋,一點擊就會出現鍵盤提供輸入,右邊會有清除文字的 Icon 跟取消搜尋。

再來加入之前篇章做好的 LeetCode 題目列表,搭配搜尋框幫助我們快速找到題目。

struct ContentView: View {
    @State private var searchText = ""
    
    
    let leetCodeProblems = [
        LeetCodeProblem(id: 1, title: "Two Sum"),
        LeetCodeProblem(id: 2, title: "Add Two Numbers"),
        LeetCodeProblem(id: 3, title: "Longest Substring Without Repeating Characters"),
        
    ]
    
    var searchResults: [LeetCodeProblem] {
            if searchText.isEmpty {
                return leetCodeProblems
            } else {
                return leetCodeProblems.filter { $0.title.contains(searchText) }
            }
        }
    
    var body: some View {
        NavigationStack {
            List(searchResults) { problem in
                NavigationLink(destination: Text(problem.title)) {
                    Text(problem.title)
                }
            }
        }
        .searchable(text: $searchText)
        
    }
    
}

把原本顯示的 leetCodeProblems 放在 List 改顯示搜尋過後過濾的 searchResults,這樣就達到效果了!

那如果希望搜尋有提供建議,可以這麼設定。

.searchable(text: $searchText){
      ForEach(searchResults, id: \.self) { result in
           Text("你在尋找 \(result.title) 嗎?").searchCompletion(result.title)
      }
 }

不過要注意資料物件要多繼承 Hashable,才可以塞在 .searchable

struct LeetCodeProblem: Identifiable, Hashable {
    var id: Int
    var title: String
}

當在搜尋時游標出現就會自動跳出搜尋提示。

想更看多進階教學,可以看以下網站,因為後續功能跟 LeetCode 題目列表需求不是太相關,故不贅述。

列表主題切分

由於前面我們介紹了不少 LeetCode 演算法主題,需要讓 LeetCode 題目跟著主題分類,不再只是顯示亂七八糟的題目讓我們無所適從。

SwiftUI 提供了 Section 可以讓列表做一個主題是分類。

我們會看到很多教學使用 Section 都是直接寫死在程式碼上,定義出哪幾個資料是要同樣主題

List {
    Section(header: Text("主題")) {
        Text("第一行")
        Text("第二行")
        Text("第三行")
    }
}

一個簡單的列表主題就會顯示,不過這樣還不夠,我們要能應用在 LeetCode 列表裡面。

資料格式需要改變變成字典 Dictionary,Key 為 LeetCode 演算法主題。

let data: [String: [LeetCodeProblem]] = [
        "Array": [LeetCodeProblem(id: 1, title: "Two Sum"),
                  LeetCodeProblem(id: 2, title: "Best Time to Buy and Sell Stock"),
                  LeetCodeProblem(id: 3, title: "Contains Duplicate"),
                  LeetCodeProblem(id: 4, title: "Product of Array Except Self"),
                  LeetCodeProblem(id: 5, title: "Maximum Subarray"),
                 ],
        "Dynamic Programming": [LeetCodeProblem(id: 6, title: "Climbing Stairs"),
                                LeetCodeProblem(id: 7, title: "Coin Change"),
                                LeetCodeProblem(id: 8, title: "Longest Increasing Subsequence")],
        "Tree": [LeetCodeProblem(id: 9, title: "Maximum Depth of Binary Tree"),
                 LeetCodeProblem(id: 10, title: "Same Tree"),]
    ]

再來搜尋後的結果資料結構邏輯也需要調整。

var searchResults: [String: [LeetCodeProblem]] {
            if searchText.isEmpty {
                return data
            } else {
                var filteredResults: [String: [LeetCodeProblem]] = [:]
                
                for (key, problems) in data {
                    let filteredProblems = problems.filter { $0.title.localizedCaseInsensitiveContains(searchText) }
                    if !filteredProblems.isEmpty {
                        filteredResults[key] = filteredProblems
                    }
                }
                
                return filteredResults
            }
        }

而核心的 SwiftUI 邏輯十分簡單,利用 ForEach 找出主題顯示在 Section 上,再一次 ForEach 去顯示主題下的題目。

var body: some View {
     
        NavigationStack {
            List{
                ForEach(searchResults.keys.sorted(), id: \.self){ key in
                    Section(header: Text(key)) {
                        ForEach(searchResults[key]!, id: \.self) { item in
                            NavigationLink(destination: Text(item.title)) {
                                Text(item.title)
                            }
                        }
                        
                    }
                }
                
            }
        }
        .searchable(text: $searchText)
        
    }

最後成果畫面如下:

成功對應主題顯示我們需要的 LeetCode 題目,且搜尋可以找到我們想要的題目。

圖片範例是透過 Coin 搜尋關鍵字,能夠成功找到 Coin Change 題目。

總結

這系列快完結了,有點不捨,其實 SwiftUI 還有很多值得去探索的地方,這次學到的功能十分實用,在查資料以及攥寫這個 App 時收穫非常多,希望在觀看的你會喜歡本篇介紹。


上一篇
Day 28: SwiftUI 展示 LeetCode 頁籤滑動換頁: TabView 實作
下一篇
Day 30: SwiftUI LeetCode 演算法 App 寫測試 Test 與完賽感想
系列文
用 SwiftUI 魔法變出 Leetcode 刷題知識學習 App!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言