본문 바로가기

iOS

[iOS] Firebase를 이용해 채팅앱 만들기 프로젝트 (2) - Cloud FireStore 서비스

저번 글에서는 Authentication 서비스를 이용해서 로그인, 회원가입, 로그아웃을 구현해보았습니다!!!

 

이번 글에서는 Cloud FireStore 서비스를 이용해서 채팅을 구현해보도록 할게요. Firebase FireStore는 NoSQL 클라우드 데이터베이스를 사용해 클라이언트 및 서버 측 개발에 사용되는 데이터를 저장하고 동기화해줍니다!!! 여기서 중요한 것은 동기화예요! 채팅에서 가장 중요한 것은 실시간으로 업데이트 되는 것입니다. Firebase는 데이터저장소에 Listener를 달아주어 업데이트가 되는 즉시, 연결되어 있는 모든 기기에 동기화를 시켜줍니다! 더 자세한 내용은 Cloud Firestore를 참고하실 수 있습니다.

 

 

채팅 UI 만들기 및 데이터 모델 만들기 (사전 준비)

저는 xib파일을 이용해서 만들어주었습니다. 위와 같이 xib를 구성해주었는데 sender가 "나"일 경우 왼쪽 그림을 숨기고, "상대방"일 경우 오른쪽 그림을 숨깁니다.

그리고 ChatViewController 파일에 xib를 등록해주었습니다.

 

messageTableView.register(UINib(nibName: "MessageCell", bundle: nil), forCellReuseIdentifier: "ReusableCell")

 

또한, TableView에 담아줄 Data Model을 만들어줍니다.

메세지를 보내는 사람 sender와 메세지를 담아줄 body를 변수로 선언해줍니다.

 

import Foundation

struct Message {
    let sender: String
    let body: String

}

 

그 후, TableViewDelegate를 지정해준 후 아래의 코드를 작성해 줍니다.

 

extension ChatViewController: UITableViewDataSource, UITableViewDelegate {

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return messages.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let messageCell = tableView.dequeueReusableCell(withIdentifier: "ReusableCell", for: indexPath) as! MessageCell

        messageCell.label.text = messages[indexPath.row].body
        return messageCell
    }
}

 

이제 사전 준비는 마쳤습니다!

 

 

 

Firebase에 데이터 저장하기

 

이제 메세지 보내는 버튼을 누르면 Firebase 데이터베이스에 저장이 되게 해주는 작업을 해보려고 합니다.

 

let db = Firestore.firestore()

var messages: [Message] = []

 

파이어베이스의 DB를 가져오고, 데이터를 담아줄 배열을 만들었습니다!

 

@IBAction func sendMessage(_ sender: UIButton) {

    if let messageBody = messageTextField.text, let messageSender = Auth.auth().currentUser?.email {
        db.collection("messages").addDocument(data: [
            "sender": messageSender,
            "body": messageBody,
            "date": Date().timeIntervalSince1970
        ]) { (error) in
            if let e = error {
                print(e.localizedDescription)
            } else {
                print("Success save data ")

                DispatchQueue.main.async {
                    self.messageTextField.text = ""
                }
            }
        }
    }

}

 

버튼을 누르는 IBAction을 만든 후, 위의 코드를 작성해줍니다. Auth.auth().currentUser?.email은 현재 로그인 되어 있는 user의 이메일을 가져옵니다. db.collection("messages")는 messages 라는 이름을 가진 collection을 찾은 후, 그 안에 데이터를 배열 형식으로 저장한다는 의미입니다. 클로저 내에서는 error가 발생했을 때의 분기처리를 해주었습니다. date도 함께 저장해주는 이유는 다시 불러올 때 시간 순서대로 정렬시키기 위해서입니다!!

 

 

Firebase에 데이터 불러오기

 

데이터를 저장은 했으니 이제 불러와야 합니다! 어떤 상황에 메세지가 업데이트되야할까 생각해보면, 처음 채팅화면에 들어왔을 때와 메세지가 업데이트 될 때입니다! 그래서 전 당연히 함수 하나를 생성한 후에 두군데에 그 함수를 호출해야 한다고 생각했습니다. 하지만 파이어베이스의 작동방법은 제가 생각한 것과 달랐습니다! 불러오는 함수를 만들 때, 실시간 업데이트를 할 수 있는 메소드가 있었습니다. 바로 addSnapshotListner()메소드로 문서를 수신 대기 상태로 만들 수 있었습니다.

 

private func loadMessages() {


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

                self.messages = []

                if let e = error {
                    print(e.localizedDescription)
                } else {
                    if let snapshotDocuments = querySnapshot?.documents {
                        snapshotDocuments.forEach { (doc) in
                            let data = doc.data()
                            if let sender = data["sender"] as? String, let body = data["body"] as? String {
                                self.messages.append(Message(sender: sender, body: body))


                                DispatchQueue.main.async {
                                    self.messageTableView.reloadData()
                                    self.messageTableView.scrollToRow(at: IndexPath(row: self.messages.count-1, section: 0), at: .top, animated: false)
                                }

                            }
                        }
                    }
                }
            }
    }

 

위 함수를 viewDidLoad()에서 한번만 호출하면 됩니다.

 

Sender에 따른 채팅 분리

메세지까지 읽어오면 messages 배열에는 파이어베이스 데이터베이스의 데이터들이 담겨져있습니다. 이 데이터들을 이제 분리시켜서 테이블뷰에 뿌려주면 됩니다.

 

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let message = messages[indexPath.row]

        let messageCell = tableView.dequeueReusableCell(withIdentifier: "ReusableCell", for: indexPath) as! MessageCell


        if message.sender == Auth.auth().currentUser?.email {
            messageCell.leftImageView.isHidden = true
            messageCell.rightImageView.isHidden = false
            messageCell.messageView.backgroundColor = UIColor.lightGray
            messageCell.label.textColor = UIColor.black
        } else {
            messageCell.leftImageView.isHidden = false
            messageCell.rightImageView.isHidden = true
            messageCell.messageView.backgroundColor = UIColor.black
            messageCell.label.textColor = UIColor.white
        }

        messageCell.label.text = message.body

        return messageCell
    }

결과

실시간으로 업데이트가 되네요 ㅎㅎ