[UIKit] Firebase Chat App: Sending Messages

Junyoung Park·2022년 9월 20일
0

UIKit

목록 보기
41/142
post-thumbnail

Swift: Firebase Chat App Part 11 - Sending Messages (Real-time) - Xcode 11 - 2020

Firebase Chat App: Sending Messages

구현 목표

  • 특정한 대상에게 텍스트 데이터 이메일 전송 구현

구현 태스크

  1. 현재 접속한 유저 데이터 파악하기
  2. 보내려는 대상의 데이터 파악하기
  3. 데이터베이스를 통해 자신이 보낸 데이터 저장하기

핵심 코드

    @objc private func didTapComposeButton() {
        let vc = NewConversationViewController()
        
        vc.completionHandler = { [weak self] result in
            guard let self = self else { return }
            self.createNewConversation(result: result)
        }
        let nav = UINavigationController(rootViewController: vc)
        present(nav, animated: true)
    }
    
    private func createNewConversation(result: [String : String]) {
        
        guard
            let name = result["name"],
            let usedEmail = result["email"],
            let platform = result["platform"] else {
            return
        }
        let email = usedEmail
        let vc = ChatViewController(with: String(email))
        vc.isNewConversation = true
        vc.title = name
        vc.navigationItem.largeTitleDisplayMode = .never
        navigationController?.pushViewController(vc, animated: true)
    }
  • 검색 바 클릭 이후 네비게이션 이동되는 뷰 컨트롤러가 사용하는 컴플리션 핸들러를 삽입하는 함수
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
        // start conversation
        let targetUserData = results[indexPath.row]
        dismiss(animated: true, completion: { [weak self] in
            guard let self = self else { return }
            self.completionHandler?(targetUserData)
        })
    }
  • 검색 바의 테이블 뷰 셀은 현재 접속한 유저의 입장에서 이메일을 보낼 대상과의 대화창을 띄우는 루트
  • 현재 검색 바를 띄우기 전 주입된 컴플리션 핸들러가 사용할 파라미터 값으로 선택받은 테이블 뷰 셀의 데이터를 주기
  • 네비게이션 뷰를 디스미스, 동시에 선택한 유저 데이터를 통해 UI를 패치한 뷰 컨트롤러를 그릴 수 있음
    private var selfSender: Sender? {
        guard
            let email = UserDefaults.standard.value(forKey: "email") as? String,
            let platformString = UserDefaults.standard.value(forKey: "platform") as? String,
            let platform = Platform(rawValue: platformString)
        else {
            return nil
        }
        let safeEmail = DatabaseManager.safeEmail(emailAddress: email, platform: platform)
        return Sender(photoURLString: "", senderId: safeEmail, displayName: "Junyeong Park")
    }
  • 현재 유저 정보를 바탕으로 selfSender를 표현하는 연산 프로퍼티
    func inputBar(_ inputBar: InputBarAccessoryView, didPressSendButtonWith text: String) {
        guard
            !text.replacingOccurrences(of: " ", with: "").isEmpty,
            let sender = selfSender,
            let messageId = createMessageId() else {
            return
        }
        print("Sending \(text)")
        // Send Message
        if isNewConversation {
            // create in database
            let message = Message(sender: sender,
                                  messageId: messageId,
                                  sentDate: Date(),
                                  kind: .text(text))
            DatabaseManager.shared.createNewConversation(with: otherUserEmail, firstMessage: message) { success in
                if success {
                    print("Message Sent!")
                }
            }
        } else {
            // append to existing conversation data
        }
    }
extension DatabaseManager {
    /*
     unique_identifier {
        "messages" : [
            {
                "id" : String,
                "type" : text, photo, video,
                "content" : String,
                "date" : Date(),
                "sender_email" : String,
                "isRead" : true/false
            }
        ]
     }
     
     
     conversation => [
        [
            "conversation_id" : unique_identifier
            "other_user_email" :
            "latest_message" : => {
                "date" : Date(),
                "latest_message" : "message",
                "is_read" : true/false
            }
        ],
     ]
     */
    
    
    /// Creates a new conversation with target user email and first message sent
    func createNewConversation(with otherUserEmail: String, firstMessage: Message, completionHandler: @escaping ((Bool) -> Void)) {
        guard
            let currentEmail = UserDefaults.standard.value(forKey: "email") as? String,
            let platformString = UserDefaults.standard.value(forKey: "platform") as? String,
            let platform = Platform(rawValue: platformString) else {
            completionHandler(false)
            return
        }
        let safeEmail = DatabaseManager.safeEmail(emailAddress: currentEmail, platform: platform)
        let ref = database.child("\(safeEmail)")
        ref.observeSingleEvent(of: .value) { snapshot in
            guard var userNode = snapshot.value as? [String : Any] else {
                completionHandler(false)
                print("User not found")
                return
            }
            let messageDate = firstMessage.sentDate
            let dateString = DateFormatter.dateFormatter.string(from: messageDate)
            var message = ""
            switch firstMessage.kind {
            case .text(let messageText):
                message = messageText
                break
            case .attributedText(_):
                break
            case .photo(_):
                break
            case .video(_):
                break
            case .location(_):
                break
            case .emoji(_):
                break
            case .audio(_):
                break
            case .contact(_):
                break
            case .linkPreview(_):
                break
            case .custom(_):
                break
            @unknown default:
                break
            }
            
            let conversationID = "conversation_" + firstMessage.messageId
            
            let newConversationData: [String:Any] = [
                "id" : conversationID,
                "other_user_email" : otherUserEmail,
                "lastest_message" : [
                    "date" : dateString,
                    "message" : message,
                    "is_read" : false
                ]
            ]
            if var conversations = userNode["conversations"] as? [[String : Any]] {
                // Conversation array exists for current user
                // you should append
                conversations.append(newConversationData)
                userNode["conversations"] = conversations
            } else {
                // Conversation array does not exist
                // Create it
                userNode["conversations"] = [
                    newConversationData
                ]
            }
            ref.setValue(userNode) { [weak self] error, _ in
                guard let self = self else { return }
                guard error == nil else {
                    completionHandler(false)
                    return
                }
                self.finishCreatingConversation(conversationID: conversationID, firstMessage: firstMessage, completionHandler: completionHandler)
            }
        }
    }
    
    /// Fetches and returns all conversations for the user with passed in email
    func getAllConversations(for email: String, completionHandler: @escaping ((Result<String, Error>) -> Void)) {
    }

    /// Gets all messages for a given conversation
    func getAllMessagesForConversation(with id: String, completionHandler: @escaping ((Result<String, Error>) -> Void)) {
        
    }
    
    /// Sends a message with target conversation and message
    func setMessage(to conversation: String, message: Message, completion: @escaping (Bool) -> Void) {
        
    }
    
    private func finishCreatingConversation(conversationID: String, firstMessage: Message, completionHandler: @escaping ((Bool) -> ())) {
        
        guard
            let currentUserEmail = UserDefaults.standard.value(forKey: "email") as? String,
            let platformString = UserDefaults.standard.value(forKey: "platform") as? String,
            let platform = Platform(rawValue: platformString) else {
            completionHandler(false)
            return
        }
        
        let email = DatabaseManager.safeEmail(emailAddress: currentUserEmail, platform: platform)
        var content = ""
        switch firstMessage.kind {
        case .text(let messageText):
            content = messageText
            break
        case .attributedText(_):
            break
        case .photo(_):
            break
        case .video(_):
            break
        case .location(_):
            break
        case .emoji(_):
            break
        case .audio(_):
            break
        case .contact(_):
            break
        case .linkPreview(_):
            break
        case .custom(_):
            break
        @unknown default:
            break
        }
        
        let messageDate = firstMessage.sentDate
        let dateString = DateFormatter.dateFormatter.string(from: messageDate)
        
        let collectionMessage: [String:Any] = [
            "id" : firstMessage.messageId,
            "type" : firstMessage.kind.messageKindString,
            "content" : content,
            "date" : dateString,
            "sender_email" : email,
            "is_read" : false
        ]
        
        let value: [String:Any] = [
            "messages" : [
                collectionMessage
            ]
        ]
        database.child("\(conversationID)").setValue(value) { error, _ in
            guard error == nil else {
                completionHandler(false)
                return
            }
            completionHandler(true)
        }
    }
}
  • 현재 유저 정보, 전송할 데이터, 상대방 유저 정보, 날짜 등 전송 한 번에 이루어지는 데 필요한 데이터베이스 관련 함수
  • setValue의 경로는 현재 유저 정보를 바탕으로 결정
  • [String:Any]의 데이터 트리 구조에 따라 데이터 모델링

구현 화면

profile
JUST DO IT

0개의 댓글