iT邦幫忙

2021 iThome 鐵人賽

DAY 25
0
自我挑戰組

來寫看看app好了! Swift探索之旅系列 第 25

Day#25 尋找其他使用者(2) 資料結構

前言

此時,我們希望可以透過query的方式將db裡的用戶都顯示出來。
因此今天將著重存在db的資料結構調整。

資料結構

現在db裡的結構如下

但是想要做到搜尋,我們沒辦法使用no-sql的資料結構去一個個找key。因此在此我們要修改一下資料結構。

思考一下對話的資料結構可能的樣貌,由於我們想要保留每個已存在的對話可以被顯示在conversation table view當中,因此每個使用者都會有自己的user array。

/*
    [
        [
            "name": ,
            "safe_email
        ],
        [
            "name": ,
            "safe_email
        ],
    ]
*/

DatabaseManager

inserUser

當我們新增(註冊)一個使用者時,在insertUser新增以下的內容

我們檢查當下是否有以這個帳號為key的array

self.ref.child("users").observeSingleEvent(of: .value, with: { snapshot in

有的話就append、沒有的話就新增。

if var usersCollection = snapshot.value as? [[String: String]] {
    // append to user dictionary
    let newElement = [
        "name": user.firstName + " " + user.lastName,
        "email": user.safeEmail
    ]
    usersCollection.append(newElement)
    self.ref.child("users").setValue(usersCollection, withCompletionBlock: { error , _ in
        guard error == nil else {
            completion(false)
            return
        }
        completion(true)
    })

} else {
    let newCollection: [[String: String]] = [
        [ "name": user.firstName + " " + user.lastName,
          "email": user.safeEmail
        ]
    ]
    self.ref.child("users").setValue(newCollection, withCompletionBlock: { error , _ in
        guard error == nil else {
            completion(false)
            return
        }
        completion(true)
    })
}
})

completion(true)

NewConversationViewController

然後我們接下來想要做的是,如果現在手邊有all user就直接filter,沒有的話就先fetch再filter。

因此我們先宣告會使用到的東西。
在此result是filter後的結果,而hasFetched則是一個開關。

private let spinner = JGProgressHUD(style: .dark)

private var users = [[String: String]]()
private var results = [[String: String]]()
private var hasFetched = false

search users

接著就是將剛剛提到的邏輯實作出來,我們使用replacingOccurrences確保query的內容已經被trim過。

extension NewConversationViewController: UISearchBarDelegate {
    func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
        guard let text = searchBar.text, !text.replacingOccurrences(of: " ", with: "").isEmpty else {
            return
        }
        
        results.removeAll()
        spinner.show(in: view)
        self.searchUsers(query: text)
    }

拿到處理好的query後,開始從db比對。

    func searchUsers(query: String) {
        // check if array has firebase result
        if hasFetched {
            self?.filterUsers(with: query)
        } else {
            // fetch then filter
            DatabaseManager.shared.getAllUsers(completion: { [weak self] result in
                switch result {
                case .success(let usersCollection):
                    self?.users = usersCollection
                    self?.filterUsers(with: query)
                case .failure(let error):
                    print("Fail to get user: \(error)")
                }
        
            })
        }
    }
    
    func filterUsers(with term: String){
        // update the UI
        guard hasFetched else {
            return
        }
        
        let results: [[String: String]] = self.users.filter {
            guard let name = $0["name"]?.lowercased() else{
                return false
            }
            return name.hasPrefix(term.lowercased())
        }
        self.results = results
        
        updateUI()
    }

最後將結果顯示出來,如果有結果就將tableView刷新顯示;沒有的話就顯示noResult的label。而這兩的內容都已經有再先前寫好了!

    func updateUI() {
        if results.isEmpty {
            self.noResultLabel.isHidden = false
            self.tableView.isHidden = true
        } else {
            self.noResultLabel.isHidden = true
            self.tableView.isHidden = false
            self.tableView.reloadData()
        }
    }
}

viewDidLoad

最後我們在Load進來的時候把subview都加進來

view.addSubview(noResultLabel)
view.addSubview(tableView)

tableView.delegate = self
tableView.dataSource = self

當然也別忘了跟隨著tableView.delegate以及tableView.dataSource的extension。

extension

extension NewConversationViewController: UITableViewDelegate, UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return results.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        cell.textLabel?.text = results[indexPath.row]["name"]
        return cell
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
        // start conversation
    }
}

結語

留了一個小伏筆,在didSelectRowAt方法中,也就是當點選某個row,即代表開始展開對話~
終於要開始實作這30天的重點了嗎(??)

若上述內容有誤或可以改進的部分,歡迎留言以及提出任何指教~
謝謝 (´・∀・`)


上一篇
Day#24 尋找其他使用者(1)
下一篇
Day#26 傳送對話(1)
系列文
來寫看看app好了! Swift探索之旅30

尚未有邦友留言

立即登入留言