Swift: Firebase Chat App Part 11 - Sending Messages (Real-time) - Xcode 11 - 2020
@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)
})
}
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]
의 데이터 트리 구조에 따라 데이터 모델링