iT邦幫忙

2021 iThome 鐵人賽

DAY 26
0
Mobile Development

在 iOS 開發路上的大小事系列 第 26

【在 iOS 開發路上的大小事-Day26】透過 Firebase 來管理資料 (Realtime Database 篇) Part2

前情提要

昨天已經將環境設定好了,今天要來將新增、讀取、更新、刪除、排序功能實作出來

開始實作

設計留言的 Model

我們會需要一個 id 來記錄這是哪一筆留言,方便我們後面來處理
然後還需要記錄留言人的名字以及留言內容跟最後留言時間
所以就可以將這些東西設計成一個 struct

此外,後面我們還有透過時間來排序留言的需求
所以我們還需要讓這個 struct 符合 Comparable 的規範,所以

struct MessageModel: Comparable {
    static func < (lhs: MessageModel, rhs: MessageModel) -> Bool {
        return lhs.time < rhs.time
    }
    
    var id: String
    var name: String
    var content: String
    var time: String
}

建立變數

宣告兩個變數,一個是資料庫的參考,一個是用來取得 Struct 內容的

var databaseRef: DatabaseReference!
var messageList = [MessageModel]()

要加到 viewDidLoad 裡面的東西

override func viewDidLoad() {
    super.viewDidLoad()
    databaseRef = Database.database().reference().child("messages") // 這個是用來告訴 Firebase,我們要找的資料庫路徑
    messageTableView.register(UINib(nibName: "RealtimeDatabaseCell", bundle: nil), forCellReuseIdentifier: "RealtimeDatabaseCell") // 因為我是用 Xib 設計畫面,所以要註冊一下
    messageTableView.delegate = self
    messageTableView.dataSource = self
    self.fetchMessageFromFirebase() // 這個後面會用到,先寫著
}

要加到 viewWillAppear 裡面的東西

用來監聽目前資料庫內的狀態,可以監聽的狀態有這些

事件類型 典型用法
FIRDataEventTypeChildAdded 檢索項列表,或監聽項列表中是否添加了新項。該事件將針對每個現有的子項觸發一次,並在每次向指定的路徑添加新的子項時再次觸發。系統將向監聽器傳遞一個包含新子項的數據的快照。
FIRDataEventTypeChildChanged 監聽列表中的項是否發生了更改。每次修改子節點時,均會觸發此事件。這包括對子節點的後代所做的任何修改。傳遞給事件監聽器的快照包含子項的更新數據。
FIRDataEventTypeChildRemoved 監聽列表中是否有項被移除。移除直接子項將會觸發此事件。傳遞給回調塊的快照包含已移除的子項的數據。
FIRDataEventTypeChildMoved 監聽經過排序的列表的項順序是否有更改。只要更新會引發子項重新排序,就會觸發此事件。該事件用於已通過 queryOrderedByChild 或 queryOrderedByValue 排序的數據。
FIRDataEventTypeValue 讀取並監聽對路徑中所有內容的更改。
``swift=
override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    databaseRef.observe(.value) { snapshot in
        if let output = snapshot.value as? [String: Any] {
            print("目前資料庫內有 \(output.count) 筆留言")
        } else {
            print("目前資料庫內沒有留言!")
        }
    }
}

要加到 viewWillDisappear 裡面的東西

有增加監聽就有移除監聽,這樣才不會浪費系統資源~

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    databaseRef.removeAllObservers()
}

要加到送出 Button 的 IBAction 裡面的東西

@IBAction func sendMessageToRealtimeDatabase(_ sender: UIButton) {
    self.sendMessageToFirebase()
}

// MARK: - 新增留言到 Firebase Realtime Database
func sendMessageToFirebase() {
    let key = databaseRef.childByAutoId().key
    let message = [
        "id": key,
        "name": messagePeopleTF.text!,
        "content": messageContentTV.text!,
        "time": self.getSystemTime()
    ]
    self.databaseRef.child("\(String(describing: key!))").setValue(message)
    CustomFunc.customAlert(title: "留言已送出!", message: "", vc: self, actionHandler: self.fetchMessageFromFirebase)
    self.messagePeopleTF.text = ""
    self.messageContentTV.text = ""
}

從 Realtime Database 裡面讀取 / 抓取資料

// MARK: - 從 Firebase Realtime Database 讀取留言
func fetchMessageFromFirebase() {
    self.databaseRef.observe(.value) { snapshot in
        if (snapshot.childrenCount > 0) {
            self.messageList.removeAll()
            for messages in snapshot.children.allObjects as! [DataSnapshot] {
                let messageObject = messages.value as? [String: AnyObject]
                let messageID = messageObject?["id"]
                let messageName = messageObject?["name"]
                let messageContent = messageObject?["content"]
                let messageTime = messageObject?["time"]

                let message = MessageModel(
                    id: messageID as! String,
                    name: messageName as! String,
                    content: messageContent as! String,
                    time: messageTime as! String
                )
                self.messageList.append(message)
            }
            self.messageTableView.reloadData()
        } else {
            self.messageList.removeAll()
            self.messageTableView.reloadData()
        }
    }
}

取得送出留言 / 更新留言的時間

func getSystemTime() -> String {
    let currectDate = Date()
    let dateFormatter: DateFormatter = DateFormatter()
    dateFormatter.dateFormat = "YYYY-MM-dd HH:mm:ss"
    dateFormatter.locale = Locale.ReferenceType.system
    dateFormatter.timeZone = TimeZone.ReferenceType.system
    return dateFormatter.string(from: currectDate)
}

將留言呈現在 TableView 上面

extension RealtimeDataBaseVC: UITableViewDelegate, UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return messageList.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "RealtimeDatabaseCell", for: indexPath) as! RealtimeDatabaseCell
        cell.messagePeople.text = messageList[indexPath.row].name
        cell.messageContent.text = messageList[indexPath.row].content
        return cell
    }

更新留言

    func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
        let editAction = UIContextualAction(style: .normal, title: "編輯") { action, view, completeHandler in
            let alertController = UIAlertController(title: "更新留言", message: "", preferredStyle: .alert)
            alertController.addTextField { textField in
                textField.text = self.messageList[indexPath.row].name
            }
            alertController.addTextField { textField in
                textField.text = self.messageList[indexPath.row].content
            }
            let updateAction = UIAlertAction(title: "更新", style: .default) { action in
                let updateMessage = [
                    "id": self.messageList[indexPath.row].id,
                    "name": alertController.textFields?[0].text!,
                    "content": alertController.textFields?[1].text!,
                    "time": self.getSystemTime()
                ]
                self.databaseRef.child("\(String(describing: self.messageList[indexPath.row].id))").setValue(updateMessage)
                CustomFunc.customAlert(title: "留言更新成功!", message: "", vc: self, actionHandler: nil)
            }
            let cancelAction = UIAlertAction(title: "取消", style: .cancel, handler: nil)
            alertController.addAction(updateAction)
            alertController.addAction(cancelAction)
            self.present(alertController, animated: true)
            completeHandler(true)
        }
        let leadingSwipeAction = UISwipeActionsConfiguration(actions: [editAction])
        editAction.backgroundColor = UIColor(red: 0.0/255.0, green: 127.0/255.0, blue: 255.0/255.0, alpha: 1.0)
        return leadingSwipeAction
    }

刪除留言

    func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
        let deleteAction = UIContextualAction(style: .destructive, title: "刪除") { action, view, completeHandler in
            self.databaseRef.child(self.messageList[indexPath.row].id).setValue(nil)
            completeHandler(true)
        }
        let trailingSwipeAction = UISwipeActionsConfiguration(actions: [deleteAction])
        return trailingSwipeAction
    }
}

要加到排序留言 Button 的 IBAction 裡面的東西

// MARK: - 留言排序
@IBAction func sortMessage(_ sender: UIButton) {
    self.sortMessageFromFirebase()
}

enum sortMode {
    case defaultSort // 預設排序 (從新到舊)
    case fromNewToOldSort // 從新到舊
    case fromOldToNewSort // 從舊到新
}

func sortMessageFromFirebase() {
    let alertController = UIAlertController(title: "請選擇留言排序方式", message: "排序方式為送出/更新留言的時間早晚", preferredStyle: .actionSheet)
    let defaultAction = UIAlertAction(title: "預設排序", style: .default) { action in
        self.sortMessageList(sortMode: .defaultSort)
    }
    let fromNewToOldAction = UIAlertAction(title: "從新到舊", style: .default) { action in
        self.sortMessageList(sortMode: .fromNewToOldSort)
    }
    let fromOldToNewAction = UIAlertAction(title: "從舊到新", style: .default) { action in
        self.sortMessageList(sortMode: .fromOldToNewSort)
    }
    let closeAction = UIAlertAction(title: "關閉", style: .cancel, handler: nil)
    alertController.addAction(defaultAction)
    alertController.addAction(fromNewToOldAction)
    alertController.addAction(fromOldToNewAction)
    alertController.addAction(closeAction)
    self.present(alertController, animated: true)
}

func sortMessageList(sortMode: sortMode) {
    if (sortMode == .defaultSort || sortMode == .fromNewToOldSort) {
        self.messageList.sort(by: >)
    } else if (sortMode == .fromOldToNewSort) {
        self.messageList.sort(by: <)
    }
    self.messageTableView.reloadData()
}

成果

本篇的範例程式碼:

  1. MessageModel.swift:Github
  2. RealtimeDataBaseVC.swift:Github

上一篇
【在 iOS 開發路上的大小事-Day25】透過 Firebase 來管理資料 (Realtime Database 篇) Part1
下一篇
【在 iOS 開發路上的大小事-Day27】透過 Firebase 來管理資料 (Cloud Firestore 篇) Part1
系列文
在 iOS 開發路上的大小事30

尚未有邦友留言

立即登入留言