[Swift/Firebase] Firebase RealTime DatabaseでシンプルなチャットUIを作る

Firebaseの初期設定自体は終わっている前提で進めます。

Firebaseコンソール画面でRealTime Databaseだけ立ち上げておいてください。

実装

Message.swift (メッセージのモデル)

import Foundation

struct Message: Identifiable {
    let id: String
    let text: String
    let sender: String
}

ChatViewModel (メッセージ送信 & 監視)

import FirebaseDatabase

final class ChatViewModel: ObservableObject {
    @Published var messages: [Message] = []
    private var ref: DatabaseReference = Database.database().reference()

    init() {
        observeMessages()
    }

    // メッセージ送信
    func sendMessage(text: String, sender: String) {
        let message = ["text": text, "sender": sender]
        ref.child("messages").childByAutoId().setValue(message)
    }

    // Database内のメッセージ監視
    func observeMessages() {
        ref.child("messages").observe(.childAdded) { snapshot in
            if let data = snapshot.value as? [String: String],
               let text = data["text"],
               let sender = data["sender"] {
                let message = Message(id: snapshot.key, text: text, sender: sender)
                DispatchQueue.main.async {
                    self.messages.append(message)
                }
            }
        }
    }
}

HomeView.swift (メインのView)

import SwiftUI

struct HomeView: View {
    @StateObject var chatViewModel = ChatViewModel()
    @State private var messageTextMichael: String = ""
    @State private var messageTextLincoln: String = ""
    @State private var senderNameMichael: String = "Michael"
    @State private var senderNameLincoln: String = "Lincoln"

    var body: some View {
        VStack {
            ScrollView {
                VStack {
                    ForEach(chatViewModel.messages) { message in
                        HStack {
                            if message.sender == senderNameMichael {
                                Spacer()
                                VStack(alignment: .trailing) {
                                    Text("\(message.sender):")
                                        .bold()
                                        .font(.system(size: 12))
                                    Text(message.text)
                                        .padding(10)
                                        .background(Color.blue.opacity(0.3))
                                        .cornerRadius(8)
                                }
                            } else if message.sender == senderNameLincoln {
                                VStack(alignment: .leading) {
                                    Text("\(message.sender):")
                                        .bold()
                                        .font(.system(size: 12))
                                    Text(message.text)
                                        .padding(10)
                                        .background(Color.green.opacity(0.3))
                                        .cornerRadius(8)
                                }
                                Spacer()
                            }
                        }
                        .padding([.leading, .trailing], 10)
                    }
                }
            }
            Divider().padding(.vertical, 10)
            HStack {
                TextField("Enter message (Michael)", text: $messageTextMichael)
                    .textFieldStyle(RoundedBorderTextFieldStyle())
                    .frame(minHeight: 30)
                Button {
                    if !messageTextMichael.isEmpty {
                        chatViewModel.sendMessage(text: messageTextMichael, sender: senderNameMichael)
                        messageTextMichael = ""
                    }
                } label: {
                    Text("Send as Michael")
                        .bold()
                        .padding()
                        .background(Color.blue)
                        .foregroundColor(.white)
                        .cornerRadius(8)
                }
            }
            .padding()
            HStack {
                TextField("Enter message (Lincoln)", text: $messageTextLincoln)
                    .textFieldStyle(RoundedBorderTextFieldStyle())
                    .frame(minHeight: 30)
                Button {
                    if !messageTextLincoln.isEmpty {
                        chatViewModel.sendMessage(text: messageTextLincoln, sender: senderNameLincoln)
                        messageTextLincoln = ""
                    }
                } label: {
                    Text("Send as Lincoln")
                        .bold()
                        .padding()
                        .background(Color.green)
                        .foregroundColor(.white)
                        .cornerRadius(8)
                }
            }
            .padding()
        }
    }
}