【SwiftUI】Firebaseを導入してデータ保存ができるようにする

## はじめに
アプリ開発を行なっていく中で、FirebaseのFirestoreを使用してデータ保存ができるようにする方法を学んだので、備忘録として内容をまとめます。
この記事では、タイトルと内容を入力して「メモを追加」をタップすると、内容がリストに追加されてFirestoreに保存されるサンプルアプリを元に解説します。

## 環境
【Xcode】16.2
【iOS】18.2
【macOS】Sequoia 15.3.1
【Firebase iOS SDK】 11.11.0

## 1. Firebaseプロジェクトを作成する

- [Firebase Console](https://console.firebase.google.com/) にアクセス
- 「プロジェクトを作成」をクリック
- プロジェクト名を入力、Google AnalyticsはオフでもOK
- 「アプリを追加」でiOSアプリを選択
- Bundle IDを入力 (Xcodeの `Signing & Capabilities` で確認)
- `GoogleService-Info.plist`をダウンロード、Xcodeプロジェクトに追加

## 2. Firebase SDK をXcodeに導入する

- Xcodeメニューから「File > Add Package Dependencies...」を選択
- パッケージURLに以下を入力

   ```
   https://github.com/firebase/firebase-ios-sdk
   ```

- バージョンは「Up to Next Major」でOK
- 必要なライブラリを追加（今回は下記の2つ）
   - FirebaseCore
   - FirebaseFirestore

## 3. Firebaseをアプリ起動時に初期化する
- App構造体（App.swift）に下記を追加して、アプリ起動時にFirebaseを初期化するようにする

```swift
import SwiftUI
import Firebase

@main
struct FirebaseTestApp: App {
    init() {
        FirebaseApp.configure()
    }

    var body: some Scene {
        WindowGroup {
            FirebasePracticeView()
        }
    }
}
```

## 4. Firestore用のデータモデルを作成する
- Firestoreと連携するため、Codableプロトコルを採用
- idには一意なUUIDを設定
```swift
import SwiftUI

struct Memo: Identifiable, Codable {
    var id: String = UUID().uuidString
    var title: String
    var content: String
}
```

## 5. Firestoreにデータを保存するViewModelを作成する
- 保存時にはドキュメントIDをメモのIDと合わせる。
```swift
import Foundation
import FirebaseFirestore

class MemoViewModel: ObservableObject {
    @Published var memos: [Memo] = []

    private let db = Firestore.firestore()
    private let collectionName = "memos"

    // Firestoreから読み込む
    func fetchMemos() {
        db.collection(collectionName).getDocuments { snapshot, error in
            if let error = error {
                print("読み込みエラー: \(error)")
                return
            }

            self.memos = snapshot?.documents.compactMap { doc in
                try? doc.data(as: Memo.self)
            } ?? []
        }
    }

    // Firestoreに保存する
    func addMemo(_ memo: Memo) {
        do {
            _ = try db.collection(collectionName).document(memo.id).setData(from: memo)
        } catch {
            print("保存エラー: \(error)")
        }
    }
}

```

## 6. Viewを作成
- onAppearでアプリ起動時にFirestoreからメモ一覧を取得。
- メモを追加したら即座にリストを更新するため、fetchMemos()を再度呼び出す。

```swift
import SwiftUI

struct FirebasePracticeView: View {
    @StateObject private var viewModel = MemoViewModel()
    @State private var title: String = ""
    @State private var content: String = ""

    var body: some View {
        NavigationView {
            VStack {
                TextField("タイトル", text: $title)
                    .textFieldStyle(RoundedBorderTextFieldStyle())
                TextField("内容", text: $content)
                    .textFieldStyle(RoundedBorderTextFieldStyle())
                Button("メモを追加") {
                    let newMemo = Memo(title: title, content: content)
                    viewModel.addMemo(newMemo)
                    title = ""
                    content = ""
                    viewModel.fetchMemos()
                }
                .padding()
                List(viewModel.memos) { memo in
                    VStack(alignment: .leading) {
                        Text(memo.title).bold()
                        Text(memo.content)
                    }
                }
            }
            .padding()
            .navigationTitle("メモ一覧")
            .onAppear {
                viewModel.fetchMemos()
            }
        }
    }
}
```

## まとめ
- GoogleService-Info.plistをXcodeプロジェクトに追加する
- FirebaseApp.configure()をアプリ起動時に実行する
- FirestoreとSwiftのデータモデルはCodableを使って連携
- ViewModelでFirestoreとのデータやりとりを担当し、Viewはデータバインディング