iOSのSwiftでFirebaseのRealtimeDatabaseを使ってみた

GoogleのFirebaseに興味があったので使ってみました。

FirebaseとはMBaaS(Mobile Backend as a Service)と呼ばれるクラウドバックエンドサービスで、クライアントサーバのサーバ側でほとんど開発を行う事なくサーバ側の必要な機能を提供してくれる大変便利なサービスです。

この記事ではXcodeのSwift＆Storyboardの組み合わせでFirebaseのRealtimeDatabase機能を使ったテストアプリを動かすまでの流れを書いていきます。

Firebaseの導入方法を知りたい方は目次にある「Xcodeプロジェクトを作成する」から「XcodeにFirebaseを導入する」までを、逆にもう導入方法はわかっているからRealtimeDatabaseの実装の仕方が知りたいなーって方は「テストアプリを実装する」から見ていたければと思います。
# 目次
[開発環境](#開発環境)
[Xcodeプロジェクトを作成する](#Xcodeプロジェクトを作成する)
[Firebaseプロジェクトを作成する](#Firebaseプロジェクトを作成する)
[XcodeにFirebaseを導入する](#XcodeにFirebaseを導入する)
[テストアプリを実装する](#テストアプリを実装する)
[テストアプリを動かしてみる](#テストアプリを動かしてみる)
[まとめ](#まとめ)
# 開発環境
* Xcode 14.3
* Swift 5.0
* Firebase 10.10.0
# Xcodeプロジェクトを作成する
**まずは最初にXcodeのプロジェクトを作成します。この手順ではAppleIDが必要になるのであらかじめAppleIDの作成を行い、Xcodeの設定から「アカウント」タブにAppleIDを紐づけておきましょう。**

「Create a new Xcode project」を選んでXcodeプロジェクトの作成画面に進みます。
![a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884661/503bb346-bb08-9761-2453-0d0c9e0bc304.png)
次に「iOS」タブから「App」選択して「Next」ボタンを押します。
※デフォルトではタブが「Multiplatform」になっていてそこでも「App」が選択できますが、それだと次の画面の設定項目で「Storyboard」が選択できないのでここでは必ず「iOS」タブの「App」を選択するようにします。
![b.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884661/5e6a0d90-a237-fe0d-7123-9603649818b1.png)
続いてプロジェクト名など必要な項目の入力や選択を行い「Next」ボタンを押します。
* Product Name：プロジェクト名（任意）
* Organization Identifier：アプリの識別情報（任意）
* Interface：「Storyboard」を選択
![c.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884661/7827bad3-874e-8e56-3ca8-e99f2c7410c4.png)
Xcodeプロジェクトが起動するのでそこから「Bundle Identifier」を覚えておきます。
※この後の「XcodeにFirebaseを導入する」手順でここの「Bundle Identifier」を使用します。
![d.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884661/52edf9ec-43f4-c83c-6a46-a1d2502fd18d.png)
# Firebaseプロジェクトを作成する
**次にFirebase側のプロジェクトを作成します。Googleのアカウントが必要なので、ない場合はアカウントを作りましょう。**
**[Firebase Console](https://console.firebase.google.com/) からFirebaseプロジェクトを作成します。**

「プロジェクトを追加」を押します。
![a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884661/8a4427ee-4a9b-f93b-f966-12d0aa526ac9.png)
Firebaseのプロジェクト名を任意でつけて「続行」ボタンを押します。
![c.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884661/e6b55b5b-3adf-686b-b261-a6a276a34a92.png)
「Googleアナリティクス」はあってもなくても良いのですが不要なのでとりあえずチェックボックスをオフにして「プロジェクトを作成」ボタンを押します。
![d.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884661/b085d514-c170-d009-b130-8179a8a2012e.png)
プロジェクトの作成処理中の画面などが表示されますがしばらくして「新しいプロジェクトの準備ができました」が表示されればOKです。その画面に「続行」ボタンが表示されるので押したら作成したFirebaseプロジェクトの画面に遷移します。
# XcodeにFirebaseを導入する
**ここでは作成したXcodeとFirebaseを紐付ける手順となります。この手順によりXcodeのプロジェクトでFirebaseが利用できるようになります。**
「iOS」ボタンを押します。
![e.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884661/7469d47a-6c7c-e403-9ea5-19e3698ba9d6.png)
「AppleバンドルID」に「Xcodeプロジェクトを作成する」で覚えておいた「Bundle Identifier」を入力し、「アプリを登録」ボタンを押します。
![f.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884661/e73781e3-61ed-1e53-fb56-89431c6acd1c.png)
「GoogleService-info.plist をダウンロード」からファイルをダウンロードして下記の説明画像のようにXcodeプロジェクトに追加します。
![g.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884661/3da0bb82-629b-296f-d7d8-2e77c201c7bd.png)
次の「Firebase SDK の追加」手順にあるURLをコピーしましょう。
![xa.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884661/e4755b9e-8848-e582-6078-b063893004e7.png)
Xcodeの「File」メニューの「Add Packages...」の画面を開いて検索ボックスに先程のURLを入力します。するとFirebaseSDKのパッケージが表示されるのでそのまま「Add Package」ボタンを押します。
![xb.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884661/343acae4-6ec1-c6af-6a7d-c5a6be21767d.png)
しばらくするとFirebaseのPackage Productリストが表示されるのでここから以下の２つにチェックボックスをつけて「Add Package」ボタンを押します。
* Firebase Analytics
* Firebase Database
![xc.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884661/7fe30d5c-8536-9922-b21f-20c256eede96.png)
XcodeプロジェクトにFirebase 10.10.0追加されたのが確認できます。
![xd.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884661/667f25b8-13ea-be6c-6b6e-a35cad844429.png)
最後に「AppDelegate.swift」にFirebaseを利用するための初期化処理コードを挿入します。
赤枠の箇所に「import Firebase」と「FirebaseApp.configure()」を挿入したら導入は完了となります。
![xe.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884661/ec93fab9-7f55-7ebb-bba8-4a633d62e5d4.png)
# テストアプリを実装する
**さて、ここからいよいよテストアプリの実装をしていきます。**
実装内容は
・Firebaseのデータベースに対して新規ユーザーの追加や既存ユーザーの接続を行う
・接続されたユーザーの名前と年齢がデータベースにリアルタイムに反映される
・接続中のユーザーをデータベース上から削除する
となります。

Main.storyboardを選択し、ViewController上に必要な以下のコントロールを配置していきます。
・データベースへ追加や接続するための「ユーザーID」の入力ボックス
・データベースへ追加や接続するための「接続」ボタン
・接続状態ラベル（##########　未接続　##########と表示されているもの）
・接続中のユーザーIDラベル(---と表示されているもの)
・接続中の名前を変更するための入力ボックス
・接続中の名前ラベル(---と表示されているもの)
・接続中の年齢を変更するための入力ボックス
・接続中の年齢ラベル(---と表示されているもの)
・接続中のユーザーを削除するための「削除」ボタン
を配置します。
![スクリーンショット 2023-06-08 18.26.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884661/0a9ba1e5-8cd3-3d32-793d-224befa64d27.png)
次にViewController.swiftを選択して下記の様にソースコードを追加します。
または、下記のソースコードをまるっとコピペでも問題ないと思います。
```swift
//
//  ViewController.swift
//  FirebaseTest
//
//  Created by oe on 2023/05/24.
//

import UIKit
import Firebase

class ViewController: UIViewController {
    
    @IBOutlet weak var targetIdText: UITextField!   // 接続先ユーザーIDの入力ボックス
    @IBOutlet weak var connectButton: UIButton!     // 接続ボタン
    
    @IBOutlet weak var stateLabel: UILabel!     // 接続状態ラベル
    @IBOutlet weak var userIdLabel: UILabel!    // 接続されたユーザーIDラベル
    @IBOutlet weak var nameText: UITextField!   // DBへリアルタイムに更新する名前の入力ボックス
    @IBOutlet weak var nameLabel: UILabel!      // DBからリアルタイムに参照された名前ラベル
    @IBOutlet weak var ageText: UITextField!    // DBへリアルタイムに更新する年齢の入力ボックス
    @IBOutlet weak var ageLabel: UILabel!       // DBからリアルタイムに参照された年齢ラベル
    @IBOutlet weak var deleteButton: UIButton!  // ユーザーの削除ボタン
    
    var user: DatabaseReference!    // 参照先DB（Userノード）※UserIdの親ノード
    var userId: DatabaseReference!  // 参照先DB（指定したUserIdノード）

    // メインビューが読み込まれた後に呼ばれる
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        self.user = Database.database().reference().child("user")
    }
    
    // 「接続」ボタンが押されたら呼ばれる
    @IBAction func userConnect(_ sender: AnyObject) {
        // ターゲットのユーザーIDから参照先DBを取得する
        self.userId = self.user.child(self.targetIdText.text!)
        let name: DatabaseReference = self.userId.child("name")
        let age: DatabaseReference = self.userId.child("age")
        
        // リアルタイムに更新するDBのノードと入力ボックス紐付ける
        name.observe(.value) { (snapshot: DataSnapshot) in
            if !snapshot.exists() { // ノードに値がなければ追加する
                name.setValue("unknown")
            }
            self.nameText.text = (snapshot.value! as AnyObject).description
            self.nameLabel.text = self.nameText.text
        }
        age.observe(.value) { (snapshot: DataSnapshot) in
            if !snapshot.exists() { // ノードに値がなければ追加する
                age.setValue("0")
            }
            self.ageText.text = (snapshot.value! as AnyObject).description
            self.ageLabel.text = self.ageText.text
        }
        self.userIdLabel.text = self.targetIdText.text // 取得したユーザーIDをラベルに表示する
        
        // 入力ボックスと接続状態ラベルを接続状態にする
        self.stateLabel.text = "########## 接続中 ##########"
        self.nameText.isEnabled = true
        self.ageText.isEnabled = true
        self.deleteButton.isEnabled = true
    }
    
    // 「削除」ボタンが押されたら呼ばれる
    @IBAction func userDelete(_ sender: AnyObject) {
        // 入力ボックスとラベル表示を未接続状態に戻す
        self.userIdLabel.text = "---"
        self.nameText.text = ""
        self.nameLabel.text = "---"
        self.ageText.text = ""
        self.ageLabel.text = "---"
        self.stateLabel.text = "########## 未接続 ##########"
        self.nameText.isEnabled = false
        self.ageText.isEnabled = false
        self.deleteButton.isEnabled = false
        
        // リアルタイムに更新されていたDBのノードと入力ボックス紐付けを解除する
        let name: DatabaseReference = self.userId.child("name")
        let age: DatabaseReference = self.userId.child("age")
        name.removeAllObservers()
        age.removeAllObservers()
        self.userId.removeValue() // ここで対象のUserを削除する
    }
    
    // DBへリアルタイムに更新する名前の入力ボックスの内容が変更される度に呼ばれる
    @IBAction func nameChanged(_ sender: AnyObject) {
        let data = ["name": self.nameText.text!]
        self.userId.updateChildValues(data)
    }
    
    // DBへリアルタイムに更新する年齢の入力ボックスの内容が変更される度に呼ばれる
    @IBAction func ageChanged(_ sender: AnyObject) {
        let data = ["age": self.ageText.text!]
        self.userId.updateChildValues(data)
    }

}
```
最後にMain.storyboardとViewController.swiftのコントロールやイベント（アクション）の紐づけを行います。
先程ViewController.swiftに実装したコードに書かれた「@IBOutlet」「@IBAction」をMain.storyboardの各コントロールに紐づけていきます。
紐づけの方法はMain.storyboardの各コントロールを選択した状態で右クリックメニューからコントロールやアクションの紐づけを行います。
以下の様に@IBOutletは「Referencing Outlets」、@IBActionは「Sent Events」の対象イベントからそれぞれ選択ドラッグして紐づけます。
![sample.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884661/8bbaef6f-a0a1-e4ef-92b0-cc8ca44f623d.gif)
Main.StoryboardとViewController.swiftの紐づけは以下の通りです。
・データベースへ追加や接続するための「ユーザーID」の入力ボックス
　→ @IBOutlet：targetIdText
・データベースへ追加や接続するための「接続」ボタン
　→ @IBOutlet：connectButton
　→ @IBAction：userConnect（Touch Down）
・接続状態ラベル（##########　未接続　##########と表示されているもの）
　→ @IBOutlet：stateLabel
・接続中のユーザーIDラベル(---と表示されているもの)
　→ @IBOutlet：userIdLabel
・接続中の名前を変更するための入力ボックス
　→ @IBOutlet：nameText
　→ @IBAction：nameChanged(Editing Changed)
・接続中の名前ラベル(---と表示されているもの)
　→ @IBOutlet：nameLabel
・接続中の年齢を変更するための入力ボックス
　→ @IBOutlet：ageText
　→ @IBAction：ageChanged(Editing Changed)
・接続中の年齢ラベル(---と表示されているもの)
　→ @IBOutlet：ageLabel
・接続中のユーザーを削除するための「削除」ボタン
　→ @IBOutlet：deleteButton
　→ @IBAction：userDelete（Touch Down）
ここまでできたら実装は完了です。
# テストアプリを動かしてみる
**ここではFirebase ConsoleでRealtimeDatabaseを作成したら、Xcodeに戻ってテストアプリをビルド＆テストアプリの起動となります。**
[Firebase Console](https://console.firebase.google.com/)に戻って「Function」を選択して「デベロッパー向けのその他の機能」にある「すべての構築の機能を表示」を押します。
![スクリーンショット 2023-06-12 20.58.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884661/3531f91b-8dce-3e8a-3564-d050c31d4fe4.png)
機能の一覧から「Realtime Database」を選択します。
![スクリーンショット 2023-06-12 20.59.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884661/3891d16b-70e8-fae8-9b1c-7169ae09883b.png)
Realtime Databaseページ先頭にある「データベースを作成」ボタンを押します。
![スクリーンショット 2023-06-12 21.00.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884661/e086bba6-e4f1-25fa-5e36-d1f3cd3672f5.png)
①データベースのオプションでは何もせずに「次へ」を押します。
![スクリーンショット 2023-06-12 21.00.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884661/b09e896c-8f76-0879-73ea-77c443f2e1ac.png)
②セキュリティルールでは「テストモードで開始する」を選択し、「有効にする」ボタンを押します。
![スクリーンショット 2023-06-12 21.01.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884661/9cc78c71-f896-88f8-1196-58ae4e825b57.png)
以下の画面が出てくればテストアプリ起動前の準備は終了です。
![スクリーンショット 2023-06-12 21.02.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884661/fb9c33ca-a796-516b-db64-57385afe97e2.png)
さて、Xodeに戻ってテストアプリを起動してみましょう。
![test_app.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2884661/4a984746-17ae-9ee7-0ae2-2f40c8068811.gif)
# まとめ
Firebaseの導入に大きなハードルもなく簡単に行うことが出来ました。アプリ側の実装もFirebaseのライブラリは他のよくあるライブラリと同じ様な感覚で使えてしかも少ないソースコードでここまで出来ちゃうのはやはり大きな魅力を感じますね。

今回はFirebaseのRealtimeDatabaseを使ってみましたが、それ以外にもまだまだAuthenticationやStorage等の機能が豊富にあるので少しの勉強と開発コストでアプリ開発の幅が大きく広がっていくのではないでしょうか。

最後にこのテストアプリでSwiftとStoryboardで実装しましたが今後はSwiftUIのフレームワークを使って実装もしてみたいです。

ただ、まだ色々とFirebaseの機能を使ってみたいので次回以降もSwiftとStoryboardを使ったFirebaseのテストアプリ実装シリーズを書いていこうかなと思います。