owned this note
owned this note
Published
Linked with GitHub
###### tags: `第13屆IT邦鐵人賽文章`
# 【在 iOS 開發路上的大小事-Day26】透過 Firebase 來管理資料 (Realtime Database 篇) Part2
# 前情提要
昨天已經將環境設定好了,今天要來將新增、讀取、更新、刪除、排序功能實作出來
# 開始實作
## 設計留言的 Model
我們會需要一個 id 來記錄這是哪一筆留言,方便我們後面來處理
然後還需要記錄留言人的名字以及留言內容跟最後留言時間
所以就可以將這些東西設計成一個 struct
此外,後面我們還有**透過時間來排序留言**的需求
所以我們還需要讓這個 struct 符合 Comparable 的規範,所以
```swift=
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 內容的
```swift=
var databaseRef: DatabaseReference!
var messageList = [MessageModel]()
```
## 要加到 viewDidLoad 裡面的東西
```swift=
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 裡面的東西
有增加監聽就有移除監聽,這樣才不會浪費系統資源~
```swift=
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
databaseRef.removeAllObservers()
}
```
## 要加到送出 Button 的 IBAction 裡面的東西
```swift=
@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 裡面讀取 / 抓取資料
```swift=
// 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()
}
}
}
```
## 取得送出留言 / 更新留言的時間
```swift=
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 上面
```swift=
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
}
```
## 更新留言
```swift=+
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
}
```
## 刪除留言
```swift=+
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 裡面的東西
```swift=
// 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()
}
```
# 成果
{%youtube ydXUzSKxi_U %}
本篇的範例程式碼:
1. MessageModel.swift:[Github](https://github.com/leoho0722/IT2021/blob/main/Day15~Day22%E3%80%81Day24~Day28/CocoaPodsDemo/CocoaPodsDemo/Model/MessageModel.swift)
2. RealtimeDataBaseVC.swift:[Github](https://github.com/leoho0722/IT2021/blob/main/Day15~Day22%E3%80%81Day24~Day28/CocoaPodsDemo/CocoaPodsDemo/FirebaseVC/RealtimeDatabaseVC/RealtimeDataBaseVC.swift)