【Swift】Firebase Realtime Database を用いてチャットアプリを爆速コーディングしてみた。

# はじめに
## 本記事で分かること
- Firebaseで新規プロジェクトの作成〜アプリにFirebaseを組み込むまで
- Firebase Realtime Databaseを用いた超簡易的なチャットアプリ

## 本記事で分からないこと
- Firebaseのアカウント作成
- いい感じのUIのアプリ

# 完成形
<blockquote class="twitter-tweet" data-lang="ja"><p lang="und" dir="ltr"><a href="https://twitter.com/hashtag/picplaypost?src=hash&amp;ref_src=twsrc%5Etfw">#picplaypost</a> <a href="https://t.co/ZXHerxwHcP">https://t.co/ZXHerxwHcP</a></p>&mdash; tetsukick (@TcarpentersR) <a href="https://twitter.com/TcarpentersR/status/1144242800319315968?ref_src=twsrc%5Etfw">2019年6月27日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# 1. Firebaseで新規プロジェクトの作成
まず、[Firebase](https://firebase.google.com) へアクセスして、コンソールへ移動。
下記の赤枠箇所より、新規プロジェクトを追加。
<img width="546" alt="Screen Shot 2019-07-02 at 3.25.07 PM.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261934/7e559110-bb46-2f72-5277-5371c48f0469.png">
下記のように、プロジェクト名、アナリティクスの地域を選択してプロジェクトを作成。

<img width="543" alt="Screen Shot 2019-07-02 at 3.25.20 PM.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261934/88755d33-544c-2bc0-7409-78cc090cc72d.png">
<img width="544" alt="Screen Shot 2019-07-02 at 3.25.45 PM.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261934/1025c0a8-18d9-2173-2624-9126460dc3cf.png">

これで作成完了。

# 2. アプリにFirebaseを導入
CocoapodsでFirebaseを導入します。
詳細は、[公式ドキュメント](https://firebase.google.com/docs/ios/setup?hl=ja)を参照してください。

**コマンド実行後は、拡張子が```.xcworkspace```となっている方からプロジェクトを開くようにしてください。**
↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
![スクリーンショット 2019-06-27 21.09.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261934/da6c6328-13fd-6deb-cf70-4fb549847202.png)

# 2. Firebaseコンソール画面でiOSアプリの作成
ここからはFirebase公式の手順に従ってください。
先程作成したプロジェクトのページから、以下の赤枠をクリック
<img width="566" alt="Screen Shot 2019-07-02 at 3.26.13 PM.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261934/a589d701-a730-b2b3-699a-ad2268391a82.png">

バンドルIDはXcode上の赤枠の項目を参照
<img width="605" alt="Screen Shot 2019-07-02 at 3.36.37 PM.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261934/4b5546ef-3980-e4af-5dc7-241ed27db57b.png">

![スクリーンショット 2019-06-27 21.22.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261934/391a630f-84e0-ac24-4e66-079629f4655d.png)

後はFirebaseの画面上のステップ通りに進めてください。

<img width="720" alt="Screen Shot 2019-07-02 at 3.39.55 PM.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261934/4130da82-2562-395e-fdbf-806f24cf1f4e.png">

<img width="713" alt="Screen Shot 2019-07-02 at 3.40.11 PM.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261934/2de3a75e-c45e-1368-fb03-3dd760e376bf.png">

こちらについては、
今回導入するのは、Firebase/core と Firebase/Database になります。

以下コマンドでpodfileの作成。

```shell:shell
pod init
```

podfileの内容は下記になります。プロジェクト名は各々変更してください。

```podfile:podfile
source 'https://github.com/CocoaPods/Specs.git'

platform :ios, '10.0'
use_frameworks!
target 'firebase-chatting' do
  pod 'Firebase/Core'
  pod 'Firebase/Database'
end
```

そしたら、以下のコマンドを実行しましょう。

```shell:shell
pod install
```

<img width="700" alt="Screen Shot 2019-07-02 at 3.40.30 PM.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261934/c602cac7-631e-0ea0-7bb6-a27c7a661227.png">

```Swift:AppDelegate.swift
import UIKit
import Firebase //←add this line

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

  var window: UIWindow?

  func application(_ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions:
      [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    FirebaseApp.configure() //←add this line
    return true
  }
}
```

画面左上の「＞」ボタンを押して、アプリをRunしてください。
<img width="404" alt="Screen Shot 2019-07-02 at 3.45.39 PM.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261934/9f56c661-c7da-63d1-c6ab-9c3020ef7f63.png">

<img width="703" alt="Screen Shot 2019-07-02 at 3.45.02 PM.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261934/d7b010b5-f017-b280-51ca-8f2e5975cb08.png">

はい！これでFirebaseの導入は完了です！！
ここまでだいたい**10秒**ですかね、、www笑

# 3. RealTimeDatabaseのルールを変更

```
{
  "rules": {
    ".read": true,
    ".write": true
  }
}
```
ルールを上記のように設定して、読み込み権限、書き込み権限を与えます。
設定する場所はこちら↓↓↓
<img width="1332" alt="Screen Shot 2019-07-02 at 3.50.56 PM.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261934/6e3743b5-570d-c5f7-afc4-8d3cffa3a91a.png">

# 4. UI画面の作成
Xcodeの右上の赤枠の箇所をクリックして、UI部品の選択画面を表示する。
![スクリーンショット 2019-06-27 21.44.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261934/3a7ac378-4737-526d-5c7b-f9e517a04715.png)

テキスト表示用のUITextViewをドラッグ＆ドロップ
![スクリーンショット 2019-06-27 21.37.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261934/b7c692e7-f0fe-911a-0704-f71462003ee2.png)

UITextViewにConstraints(制約)を付与
今回はSafeAreaに対して、余白15の制約をつけます。
![スクリーンショット 2019-06-27 21.38.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261934/8fd8bc3c-5083-9ec6-35a1-8e1dedb33ca9.png)

画面下部にテキスト入力用のUIViewをドラッグ＆ドロップ
![スクリーンショット 2019-06-27 21.38.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261934/f655a309-496f-2677-d608-59877718a763.png)

UIViewの制約をSafeAreaに対して、左0,右0,下0で付与する
![スクリーンショット 2019-06-27 21.56.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261934/e0287fc1-813f-1da4-44fe-1ad5088c8f44.png)

UIViewの中に入力用のUITextFieldを配置（２つ）
![スクリーンショット 2019-06-27 21.40.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261934/47b92f27-a162-f355-6427-0aece6f49395.png)

UIViewの中にコメント送信用のボタンを配置
![スクリーンショット 2019-06-27 21.40.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261934/f6d9da7b-6e55-ec76-6979-8d6263adce0e.png)

各部品に制約を付与
![スクリーンショット 2019-06-27 21.42.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261934/0cb6b30c-dea7-776f-d77f-cc2eea556c85.png)
![スクリーンショット 2019-06-27 21.43.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261934/b9fce0bd-19a2-bd45-f4ad-c207f566fc4c.png)
![スクリーンショット 2019-06-27 21.43.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261934/4f564914-83a6-07fc-c2fa-b3f121702857.png)

これで一旦画面は作成完了です。
![スクリーンショット 2019-06-27 22.01.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261934/65a8bab9-eb5f-38bf-52ca-2978f721ba24.png)

# 5.配置したUI部品とコードの紐づけ
Xcode右上の赤枠の箇所を選択して、画面分割させます。
![スクリーンショット 2019-06-27 22.03.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261934/6f7740fd-6f09-2457-241f-ee34f6cf2d94.png)

左側がstoryboard。右側が```ViewControlle.swift```が開かれている状態にします。
![スクリーンショット 2019-06-27 22.05.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261934/b21edc02-236f-7182-9afb-d90cb8007408.png)

左のstoryBoardから、controlを押しながらドラッグ＆ドロップします。
名前は適当につけてください。
UITextViewとUITextFieldを紐づけてください。
![スクリーンショット 2019-06-27 22.09.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261934/8ba1f770-fcb9-ed25-deaa-975f75f192c0.png)

ボタンはActionを指定して同じように紐づけてください。
![スクリーンショット 2019-06-27 22.13.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261934/4852bf9d-9f2f-7498-b9e9-0291a341804d.png)

最後に、画面下部のView.bottomとSafeArea.bottomにつけられている制約も同じくドラッグ＆ドロップで紐づけます。
![スクリーンショット 2019-06-27 22.15.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261934/ca4697c7-5162-5302-a08e-1afded4e18b4.png)

# 6. データの読み込み処理
### 初期設定

`Database.database().reference()`でインスタンスを取得します。

```Swift:ViewController.swift
    var databaseRef: DatabaseReference!

    override func viewDidLoad() {
        super.viewDidLoad()
        
        databaseRef = Database.database().reference()
        
```

### データの読み込み

`observe`でイベントの監視を行います。
`.childAdded`を指定することで、子要素が追加されたときにwithで与えた処理が実行されます。
データに追加があると自動で呼ばれるので、更新処理を実装する必要はありません。

```Swift:ViewController.swift
    override func viewDidLoad() {
        super.viewDidLoad()
        
        databaseRef = Database.database().reference()
        
        databaseRef.observe(.childAdded, with: { snapshot in
            if let obj = snapshot.value as? [String : AnyObject], let name = obj["name"] as? String, let message = obj["message"] {
                let currentText = self.textView.text
                self.textView.text = (currentText ?? "") + "\n\(name) : \(message)"
            }
        })
        
    }
```

# 7. データの書き込み
ボタンを押した時に、UITextField２つに入力されている値を送信します。

```Swift:ViewController.swift
    @IBAction func tappedSendButton(_ sender: Any) {
        view.endEditing(true)
        
        if let name = nameInputView.text, let message = messageInputView.text {
            let messageData = ["name": name, "message": message]
            databaseRef.childByAutoId().setValue(messageData)
            
            messageInputView.text = ""
        }
    }
```

ここまでで、一旦動かしてみてください。
入力時に入力域がキーボードで隠れてしまいますよね、、
なので、ここからはおまけ的にキーボードに入力域が隠れないようにする方法を紹介します。

# 8. 入力域がキーボードで隠れないように動的に入力域の位置を変更

ViewDidload内に以下を追記。
こちらは、キーボードが表示されるタイミングと非表示になるタイミングを監視してくれるものになります。

```Swift:ViewController.swift
        NotificationCenter.default.addObserver(self, selector: #selector(ViewController.keyboardWillShow(_:)), name: UIResponder.keyboardWillShowNotification, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(ViewController.keyboardWillHide(_:)), name: UIResponder.keyboardDidHideNotification, object: nil)
```

そしたら、上記によって呼び出されるメソッドにて入力域の末端の制約を動的に変更させます。

```Swift:ViewController.swift
    @objc func keyboardWillShow(_ notification: NSNotification){
        if let userInfo = notification.userInfo, let keyboardFrameInfo = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue {
            inputViewBottomMargin.constant = keyboardFrameInfo.cgRectValue.height
        }
        
    }
    
    @objc func keyboardWillHide(_ notification: NSNotification){
        inputViewBottomMargin.constant = 0
    }
    
```

# 9. 完成
これで完成です！！

最後にViewControllerの全貌を貼っておきます。

```Swift:ViewController.swift
import UIKit
import FirebaseDatabase

class ViewController: UIViewController {
    
    @IBOutlet weak var textView: UITextView!
    @IBOutlet weak var nameInputView: UITextField!
    @IBOutlet weak var messageInputView: UITextField!
    @IBOutlet weak var inputViewBottomMargin: NSLayoutConstraint!
    
    var databaseRef: DatabaseReference!

    override func viewDidLoad() {
        super.viewDidLoad()
        
        databaseRef = Database.database().reference()
        
        databaseRef.observe(.childAdded, with: { snapshot in
            dump(snapshot)
            if let obj = snapshot.value as? [String : AnyObject], let name = obj["name"] as? String, let message = obj["message"] {
                let currentText = self.textView.text
                self.textView.text = (currentText ?? "") + "\n\(name) : \(message)"
            }
        })
        
        NotificationCenter.default.addObserver(self, selector: #selector(ViewController.keyboardWillShow(_:)), name: UIResponder.keyboardWillShowNotification, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(ViewController.keyboardWillHide(_:)), name: UIResponder.keyboardDidHideNotification, object: nil)
        
    }

    @IBAction func tappedSendButton(_ sender: Any) {
        view.endEditing(true)
        
        if let name = nameInputView.text, let message = messageInputView.text {
            let messageData = ["name": name, "message": message]
            databaseRef.childByAutoId().setValue(messageData)
            
            messageInputView.text = ""
        }
    }
    
    @objc func keyboardWillShow(_ notification: NSNotification){
        if let userInfo = notification.userInfo, let keyboardFrameInfo = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue {
            inputViewBottomMargin.constant = keyboardFrameInfo.cgRectValue.height
        }
        
    }
    
    @objc func keyboardWillHide(_ notification: NSNotification){
        inputViewBottomMargin.constant = 0
    }
    
}
```

# 最後に
実際にだいたい1時間くらいで実装できてしまったので、ほんとに驚きです。
今後Firebaseの諸々を調査していく予定です。

よかったらいいねください！！

# 参考記事
[Firebase を使って30分でiOSのチャットアプリを作ってみる(新SDK対応版)](http://crossbridge-lab.hatenablog.com/entry/2016/06/02/085726)
[【Swift】リアルタイムチャットを実現するFirebaseでCRUD(データ作成、読み込み、更新、削除)をやってみる](https://qiita.com/ShinokiRyosei/items/f71c73ab8b0de145c5bc)

# GitHubはこちら
https://github.com/Tetsukick/Firebase-chat-iOS