[Swift] 大家來聊天-連接FireStore

firebase其實還有一個 realtime database,不過感覺都快要被放棄了…這次用的是主推的fireStore

連接資料庫

打開 firebase後點選右邊的 cloud fireStore,新增一個資料庫

選擇測試模式

由於之前已經在串接登入及註冊時順手安裝 firestore了,這裡可以直接跳過安裝步驟

要使用時只要插入以下的程式碼就能使用了

let db = Firestore.firestore()

回到聊天室主頁,新增送出訊息的 IBAction

在有登入且訊息不為空時儲存到 firestore

@IBAction func sendMessage(_ sender: UIControl) {
if let messageBody = messageInput.text, let account = Auth.auth().currentUser?.email {

let time = Date()

db.collection("messages").addDocument(data: ["account":
account, "message": messageBody, "time": time]) {
(error) in
if let error = error {
print(error.localizedDescription)
} else {
print("Successfully saved data.")
self.messageInput.text = nil
}
}
}
}

然後在畫面載入時向 firestore取得訊息列表

    // 不需要假資料了
var messages: [Message] = []

override func viewDidLoad() {
super.viewDidLoad()

...

loadMessages()
}
func loadMessages(){
messages = []

db.collection("messages").getDocuments { (querySnapshot,
error) in
if let error = error {
print(error.localizedDescription)
} else {
if let snapshotDocument = querySnapshot?.documents {
for doc in snapshotDocument {
let data = doc.data()
// 確認值不為空if let account = data["account"] as? String, let message = data["message"] as? String, let timestamp = data["time"] as? Timestamp {// 解析timestamp
let time = timestamp.dateValue()
let newMessage = Message(sender: account, content: message, time: time)

self.messages.append(newMessage)

DispatchQueue.main.async {
self.messageTableView.reloadData()
}

} else {
print("sth wrong")
}

}
}
}
}

}

不過這種寫法只會在初次取得資料,在訊息有增加時畫面不會有所變化,因此這裡要改用 addSnapshotListener

func loadMessages(){
messages = []

db.collection("messages").addSnapshotListener {
(querySnapshot, error) in
// db.collection("messages").getDocuments { (querySnapshot,
error) in

不過這麼一來,由於清空資料的動作在 closure外側,導致每次抓取資料時不會清空現有的訊息,因此可以把清空的動作放進 closure中

func loadMessages(){db.collection("messages").addSnapshotListener {   
(querySnapshot, error) in
self.messages = []

接著因為訊息取回來順序是亂的,因此利用 timestamp來排序

db.collection("messages").order(by: "time").addSnapshotListener {   
(querySnapshot, error) in
...

如此一來取回的資料都是經過時間排序的

鍵盤擋住輸入框

如同上圖,現在鍵盤升起來會擋到訊息框,要調整也不是容易的事,畢竟手機型號不同鍵盤尺寸也不同,這個時候可以使用第三方套件 IQKeyboardManager

pod 'IQKeyboardManagerSwift'

嗯…原本想用的

但發現用起來目前不支援botttom tabbar,位置會怪怪的,只好自己來寫功能,概念上就是在鍵盤出現時整個 view上移,然後修正 textfield和 tableview的條件

extension ChatMainViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// 加入監聽
NotificationCenter.default.addObserver(self, selector:
#selector(keyboardWillDisappear), name:
UIResponder.keyboardWillHideNotification, object: nil)
NotificationCenter.default.addObserver(self, selector:
#selector(keyboardWillAppear), name:
UIResponder.keyboardWillShowNotification, object: nil)
}

@objc func keyboardWillAppear(_ notification: Notification) {
if let keyboardFrame: NSValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue {
let keyboardRectangle = keyboardFrame.cgRectValue
// 取得鍵盤高度
let keyboardHeight = keyboardRectangle.height
// 取得tabbar高度
let tabbarHeight = messageWrapper.safeAreaInsets.bottom

// 移動view的基準點到一個鍵盤高
messageWrapper.frame.origin.y = -keyboardHeight
// textfield底部條件改為tabbar高度
textfieldBottomConstraints.constant = tabbarHeight
// tableview頂部條件向下壓一個鍵盤高
tableViewTopConstraints.constant = keyboardHeight
messageWrapper.layoutIfNeeded()

// tableview滾到底部
let indexPath = IndexPath(row: messages.count - 1, section: 0)
messageTableView.scrollToRow(at: indexPath, at: .top, animated: false)
}
}

@objc func keyboardWillDisappear() {
// 復原
messageWrapper.frame.origin.y = 0
textfieldBottomConstraints.constant = 0
tableViewTopConstraints.constant = 0
messageWrapper.layoutIfNeeded()
}

override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// 移除監聽
NotificationCenter.default.removeObserver(self)
}
}

然而此時點擊訊息畫面(tableView)時,鍵盤並不會自動收起來,這時可以在鍵盤升起來時監聽手勢,落下來時移除監聽

@objc func keyboardWillAppear(_ notification: Notification) {
if let keyboardFrame: NSValue = notification.userInfo?
[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue {
......

// add gesture recognizer
let tap = UITapGestureRecognizer(target: self, action:
#selector(UIInputViewController.dismissKeyboard))
self.messageTableView.addGestureRecognizer(tap)
}
}

@objc func keyboardWillDisappear() {
// remove gesture recognizer
self.messageTableView.gestureRecognizers?
.forEach(self.messageTableView.removeGestureRecognizer)
}

由於監聽手勢會影響其他 action,像是點擊事件,所以這裡選擇鍵盤升起時才監聽,鍵盤未升起時還是能點擊泡泡

最後把決定 cell種類的判斷式換掉

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// decide self or other
var isSelf = false

if messages[indexPath.row].sender == Auth.auth().currentUser?.email {
isSelf = true
}
// if indexPath.row % 2 == 0 {
// testResult = true
// }

這樣就可以比對訊息是否為自己發送的

最後錄個總成果:

GitHub:

這個 App能做的東西還有很多,例如說傳送含照片的message,或是添加登入失敗的警告,或者是上傳大頭貼…等,不過也練習到了非常多的東西,希望以後有機會也能參與這種產品的開發。

--

--