【在 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

<<:  企划实现(23)

>>:  Vue.js框架的便利与属性介绍 (DAY25)

.Net Core Web Api_笔记04_HTTP资源操作模式Put

基本上跟POST是有点类似的作法 通常用於资料更新(所以资料必须是已存在於目前db的) 新增额外的a...

【Day 06】LeetCode:Two Sum ( 用 JavaScript 学演算法 )

我们继续透过 LeetCode #1 Two Sum 来实际感受解决问题的过程 ( 题目连结 ) 一...

06. DB x tinker x seeder

连线设定其实也没什麽好介绍的,改 env 这种事 sail 已经弄好了。 即便现在,用 compos...

Laravel 实战经验分享 - Day29 剩下最後的两篇,该讲些什麽呢?

到了倒数第二天,一直在想自己该写什麽,在参加这个比赛之前,自己常埋首在自以为是的开发中,无论遇到什麽...

来认识 PHP 与 Laravel

PHP 干古 最早於 1994 由 Rasmus Lerdorf 用 C 语言开发的 CGI 程序,...