[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,或是添加登入失敗的警告,或者是上傳大頭貼…等,不過也練習到了非常多的東西,希望以後有機會也能參與這種產品的開發。