【Swift】リアルタイムチャットを実現するFirebaseでCRUD(データ作成、読み込み、更新、削除)をやってみる

#はじめに
本記事では、現地時間2016年5月18日（水）〜20日（金）に開催された**Google I/O**で発表された[**Firebase**](https://firebase.google.com/)の統合プラットフォームとしての拡充による、Firebaseのアップデートに対応したFirebaseをiOSでCRUDとAuthを試す記事になります。

以下、目次
1. [Firebase](https://console.firebase.google.com/)での新規プロジェクトの作成
2. [XcodeプロジェクトへのFirebaseの導入](http://qiita.com/ShinokiRyosei/items/f71c73ab8b0de145c5bc#2-xcode%E3%83%97%E3%83%AD%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%B8%E3%81%AEfirebase%E3%81%AE%E5%B0%8E%E5%85%A5)
3. [XcodeプロジェクトとFirebaseのコネクト](http://qiita.com/ShinokiRyosei/items/f71c73ab8b0de145c5bc#3-xcode%E3%83%97%E3%83%AD%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%A8firebase%E3%81%AE%E3%82%B3%E3%83%8D%E3%82%AF%E3%83%88)
4. [ユーザー認証](http://qiita.com/ShinokiRyosei/items/f71c73ab8b0de145c5bc#4-%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E8%AA%8D%E8%A8%BC)
5. [Create(データの作成、送信)](http://qiita.com/ShinokiRyosei/items/f71c73ab8b0de145c5bc#5-create%E3%83%87%E3%83%BC%E3%82%BF%E3%81%AE%E4%BD%9C%E6%88%90%E9%80%81%E4%BF%A1)
6. [Read(データの読み込み)](http://qiita.com/ShinokiRyosei/items/f71c73ab8b0de145c5bc#6-read%E3%83%87%E3%83%BC%E3%82%BF%E3%81%AE%E8%AA%AD%E3%81%BF%E8%BE%BC%E3%81%BF)
7. [Update(データの更新)](http://qiita.com/ShinokiRyosei/items/f71c73ab8b0de145c5bc#7-update%E3%83%87%E3%83%BC%E3%82%BF%E3%81%AE%E6%9B%B4%E6%96%B0)
8. [Delete(データの削除)](http://qiita.com/ShinokiRyosei/items/f71c73ab8b0de145c5bc#8-delete%E3%83%87%E3%83%BC%E3%82%BF%E3%81%AE%E5%89%8A%E9%99%A4)
9. [サンプルコード](http://qiita.com/ShinokiRyosei/items/f71c73ab8b0de145c5bc#9-%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB%E3%82%B3%E3%83%BC%E3%83%89)

では、実際にやっていきましょう！

# 1. Firebaseでの新規プロジェクトの作成

まず、[Firebase](https://firebase.google.com/)へアクセスして、アカウントを作成しましょう。

![Firebase_2.png](https://qiita-image-store.s3.amazonaws.com/0/75302/2a857fef-6ca6-a2a4-21f7-16ed3c37c846.png)

右上のログインというところから、アカウントを作成します。

続いて、Firebaseのプロジェクトを作成しましょう！
アカウント作成後に、[Firebaseのコンソール](https://console.firebase.google.com/)にアクセスすると以下のようなページが表示されると思います。

![Firebase_1.png](https://qiita-image-store.s3.amazonaws.com/0/75302/2488b028-ece6-f1bf-e3d8-83621bd65f33.png)

まだプロジェクトを作成していない場合では、作成したプロジェクトは並んでいない状態かと思います。

では、プロジェクトを作成しましょう。

![Firebase.png](https://qiita-image-store.s3.amazonaws.com/0/75302/06de96fd-6c25-196d-7a2d-34465af50dd3.png)

「新規プロジェクトを作成」から新規プロジェクトを作成しましょう。
「新規プロジェクトを作成」を選択すると、以下のようなフォームがでると思います。

![Firebase_5.png](https://qiita-image-store.s3.amazonaws.com/0/75302/ef849d17-dde3-3239-eede-fee4f865c476.png)

アプリのプロジェクト名と国を選びましょう。
プロジェクト名はアプリの名前と同じがいいかと思います。国も日本でいいと思います。
記入すると、以下のスクリーンショットのように、作成ボタンが選択可能になると思いますので、そのまま作成を選びましょう。

![Firebase_4.png](https://qiita-image-store.s3.amazonaws.com/0/75302/17262546-6f48-a5e5-5a4d-f95721a8e1d5.png)

![Firebase_6.png](https://qiita-image-store.s3.amazonaws.com/0/75302/639772be-c7ad-44f6-abb1-278ff6eae871.png)

以上のように、コンソール画面が表示されたと思います。

これで、Firebaseでの新規プロジェクトの作成は完了です。
続いて、XcodeプロジェクトにFirebaseを導入しましょう。

# 2. XcodeプロジェクトへのFirebaseの導入

今回はCocoapodsでFirebaseを導入します
使用するのは、Firebase AuthとFirebase Databaseです。
Firebase Authはユーザー認証のために、導入します。
Firebase Databaseはデータの送受信と保存のために、導入します。

では、ターミナルで導入するXcodeプロジェクトへディレクトリ移動を行い、```pod init```を行いましょう。

Cocoapodsの使い方はこちら
[[Swift]Cocoapods導入手順](http://qiita.com/ShinokiRyosei/items/3090290cb72434852460)

```:Shell
pod init
```

使用するpodはこちら

```
pod 'Firebase'
pod 'Firebase/Auth'
pod 'Firebase/Database'
```

Podfileが以下のようになっていれば大丈夫です。今回、私はXcodeプロジェクトの名称を「*Qiita-Firebase*」としました。重要なのは、```pod 'Firebase'```からの3行の位置ですので、そこを注意していただけると大丈夫かと思います。

```:Podfile
# Uncomment this line to define a global platform for your project
# platform :ios, '9.0'

target 'Qiita-Firebase' do
  # Comment this line if you're not using Swift and don't want to use dynamic frameworks
  use_frameworks!

  # Pods for Qiita-Firebase
  pod 'Firebase' //この行を追加
  pod 'Firebase/Auth' //この行を追加
  pod 'Firebase/Database' //この行を追加

  target 'Qiita-FirebaseTests' do
    inherit! :search_paths
    # Pods for testing
  end

  target 'Qiita-FirebaseUITests' do
    inherit! :search_paths
    # Pods for testing
  end

end
```

では、```pod install```を行い、podを導入しましょう。

```pod install```が終われば、拡張子が```.xcworkspace```となっているファイルを開けば、Firebaseのフレームワークは入っています。

# 3. XcodeプロジェクトとFirebaseのコネクト

[Firebaseのコンソール画面](https://console.firebase.google.com)から、プロジェクトを選択した画面から、今度はXcodeプロジェクトとFirebaseを連携させましょう。

プロジェクトの画面からiOSを選択してください。選択すると以下のようなフォームが出てくると思います。

![Firebase_14.png](https://qiita-image-store.s3.amazonaws.com/0/75302/fc83f6a6-f3d3-64d4-d8ce-80884044e100.png)

XcodeプロジェクトのBundle Identifierを記入しましょう。必要ならApp Store IDも記入してください。

![Firebase_13.png](https://qiita-image-store.s3.amazonaws.com/0/75302/f657e015-355b-7d65-eaac-9afab6fe09b0.png)

記入すると、追加ボタンが選択可能になりますので、そのまま選択してください。

XcodeのBundle Identifierがわからない方は、以下を参照してください。

![Qiita-Firebase_xcodeproj.png](https://qiita-image-store.s3.amazonaws.com/0/75302/0f04b33b-59cc-3480-b874-807ad79924a1.png)


選択すると、次の行程に移ります。そのまま続行に移り、GoogleService-Info.plistをダウンロードしましょう。

![Firebase_11.png](https://qiita-image-store.s3.amazonaws.com/0/75302/9b4a3b5f-1678-a31b-0596-704214a1e74d.png)

![Firebase_12.png](https://qiita-image-store.s3.amazonaws.com/0/75302/7b4e1d77-5e3f-bc01-36d1-5f1dae78eff2.png)

続いて、CocoapodsでのFirebaseのインポートを促されますが、今回は既に済ませていますので、スルーしてください。

![Firebase_10.png](https://qiita-image-store.s3.amazonaws.com/0/75302/758229d6-aa07-b629-c113-ea2809c3fd9e.png)

続いて、Firebaseの誘導に従って、ダウンロードしたGoogleService-info.plistをXcodeプロジェクトにコピーし、AppDelegate.swiftにコードを書きます。

では、以下のフォームに従ってコードを書きましょう。

![Firebase_9.png](https://qiita-image-store.s3.amazonaws.com/0/75302/33c62cee-22ea-b8aa-300c-01187bfe858d.png)

```:AppDelegate.swift
import UIKit
import Firebase //この行を追加

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?


    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        // Override point for customization after application launch.
     
        FIRApp.configure() //この行を追加
        return true
    }
```

GoogleService-info.plistも追加ししましょう。
![デスクトップ_と_Qiita-Firebase_xcodeproj.png](https://qiita-image-store.s3.amazonaws.com/0/75302/6deb6a23-5002-489f-c4fa-3d407c80f652.png)

これでXcodeプロジェクトとFirebaseとのコネクトは完了です。
以上で、Firebaseとの設定部分は終了です。

ではユーザー認証とCRUDのコードに移っていきましょう。

# 4. ユーザー認証

今回は、SignupViewControllerとLoginViewControllerをつくり、認証が済んでいる場合、TableViewでデータを表示するListViewControllerに遷移するようにします。

今回は、メールアドレスとパスワードでユーザー認証を行います。

画面構成としては以下の通りです。

<img width="832" alt="Main_storyboard_—_Edited.png" src="https://qiita-image-store.s3.amazonaws.com/0/75302/619be74f-dec9-e615-b03d-67eec877d190.png">

## サインアップ
では、まずSignupViewControllerにTextFieldとSign upボタン、LoginViewControllerへ遷移するボタンをを画面に追加し、そのコードを書きます。

```swift:SignupViewController.swift
import UIKit
import Firebase //Firebaseをインポート

class SignupViewController: UIViewController, UITextFieldDelegate {
    
    @IBOutlet var emailTextField: UITextField! // Emailを打つためのTextField
    
    @IBOutlet var passwordTextField: UITextField! //Passwordを打つためのTextField
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        emailTextField.delegate = self //デリゲートをセット
        passwordTextField.delegate = self //デリゲートをセット
        passwordTextField.isSecureTextEntry = true // 文字を非表示に
        
        self.layoutFacebookButton()
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    //サインアップボタン
    @IBAction func willSignup() {
        //サインアップのための関数
        signup()
    }
    //ログイン画面への遷移ボタン
    @IBAction func willTransitionToLogin() {
        transitionToLogin()
    }
    
    @IBAction func willLoginWithFacebook() {
        self.loginWithFacebook()
    }
    
    //ログイン画面への遷移
    func transitionToLogin() {
        self.performSegue(withIdentifier: "toLogin", sender: self)
    }
    //ListViewControllerへの遷移
    func transitionToView() {
        self.performSegue(withIdentifier: "toView", sender: self)
    }
    //Returnキーを押すと、キーボードを隠す
    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        textField.resignFirstResponder()
        return true
    }

```

次に、サインアップの処理を行うメソッドを書きます。

```swift:SignupViewController.swift
    //Signupのためのメソッド
    func signup() {
        //emailTextFieldとpasswordTextFieldに文字がなければ、その後の処理をしない
        guard let email = emailTextField.text else  { return }
        guard let password = passwordTextField.text else { return }
        //FIRAuth.auth()?.createUserWithEmailでサインアップ
        //第一引数にEmail、第二引数にパスワード
        FIRAuth.auth()?.createUser(withEmail: email, password: password, completion: { (user, error) in
            //エラーなしなら、認証完了
            if error == nil{
                        // エラーがない場合にはそのままログイン画面に飛び、ログインしてもらう
                        self.transitionToLogin()
                })
            }else {
                
                print("\(error?.localizedDescription)")
            }
        })
    }
```

以上でサインアップが完了です。続いて、LoginViewControllerにログインのためのコードを書きます。

## ログイン

サインアップと同じように、EmailとPasswordのためのTextFieldとログインボタンを追加し、必要なコードを以下のように追加

```swift:LoginViewController.swift
import UIKit
import Firebase //Firebaseをインポート

class LoginViewController: UIViewController, UITextFieldDelegate {
    
    @IBOutlet var emailTextField: UITextField! // Emailを打つためのTextField
    
    @IBOutlet var passwordTextField: UITextField! //Passwordを打つためのTextField

    override func viewDidLoad() {
        super.viewDidLoad()

        emailTextField.delegate = self //デリゲートをセット
        passwordTextField.delegate = self //デリゲートをセット
        passwordTextField.isSecureTextEntry  = true // 文字を非表示に
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    //ログインボタン
    @IBAction func didRegisterUser() {
        //ログインのためのメソッド
        login()
    }
    //Returnキーを押すと、キーボードを隠す
    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        textField.resignFirstResponder()
        return true
    }
    
    //ログイン完了後に、ListViewControllerへの遷移のためのメソッド
    func transitionToView()  {
        self.performSegue(withIdentifier: "toVC", sender: self)
    }
}
```

次に、ログインの処理を行うメソッドを書きます。

```swift:LoginViewController.swift
    //ログインのためのメソッド
    func login() {
            //EmailとPasswordのTextFieldに文字がなければ、その後の処理をしない
            guard let email = emailTextField.text else { return }
            guard let password = passwordTextField.text else { return }
            
            //signInWithEmailでログイン
            //第一引数にEmail、第二引数にパスワードを取ります
            FIRAuth.auth()?.signIn(withEmail: email, password: password, completion: { (user, error) in
                //エラーがないことを確認
                if error == nil {
                    if let loginUser = user {
                            self.transitionToView()
                    }
                }else {
                    print("error...\(error?.localizedDescription)")
                }
            })
    }
```

以上で、サインアップとログインの処理が完了しました。

## ログアウト

ログアウト機能は、サンプルでは、ViewControllerに追加しています。

```swift:ViewController.swift
    func logout() {
        do {
            //do-try-catchの中で、FIRAuth.auth()?.signOut()を呼ぶだけで、ログアウトが完了
            try FIRAuth.auth()?.signOut()
            
            //先頭のNavigationControllerに遷移
            let storyboard = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "Nav")
            self.present(storyboard, animated: true, completion: nil)
        }catch let error as NSError {
            print("\(error.localizedDescription)")
        }
        
    }
        
    }
```

## ユーザー認証におけるメールの確認

以上で、ユーザーのサインアップやログインは完了していますが、アプリとして使うためには、メールが本当に正しいか確認する必要があります。
さきほど書いたコードにメールのバリデーションのためのコードを追記していきたいと思います。

まず、サインアップの際に登録されたメールアドレスにバリデーションのメールを送りましょう。
そのために使用するメソッドは```sendEmailVerificationWithCompletion```です。

```createUserWithEmail``` をつかって登録した際に、エラーがない場合に、 ```sendEmailVerificationWithCompletion``` を使用します。これだけでバリデーションのためのメールを送ってくれます。

先ほどの、signupを以下のように変更します。変更している点は、```sendEmailWithCompletion```から7行のみです。


```swift:SignupViewController.swift

    //Signupのためのメソッド
    func signup() {
        //emailTextFieldとpasswordTextFieldに文字がなければ、その後の処理をしない
        guard let email = emailTextField.text else  { return }
        guard let password = passwordTextField.text else { return }
        //FIRAuth.auth()?.createUserWithEmailでサインアップ
        //第一引数にEmail、第二引数にパスワード
        FIRAuth.auth()?.createUser(withEmail: email, password: password, completion: { (user, error) in
            //エラーなしなら、認証完了
            if error == nil{
                // メールのバリデーションを行う
                user?.sendEmailVerification(completion: { (error) in
                    if error == nil {
                        // エラーがない場合にはそのままログイン画面に飛び、ログインしてもらう
                        self.transitionToLogin()
                    }else {
                        print("\(error?.localizedDescription)")
                    }
                })
            }else {
                
                print("\(error?.localizedDescription)")
            }
        })
    }
```

また、ログイン画面では、ログインボタンが押された時に、そのユーザーのメールのバリデーションが完了済みかどうか確認してから、ログインするようにします。

FIRUserの```emailVerified```を使用することで、メールのバリデーションができているかのBool値が返ってきます。

```swift:LoginViewController.swift
    //ログインのためのメソッド
    func login() {
            //EmailとPasswordのTextFieldに文字がなければ、その後の処理をしない
            guard let email = emailTextField.text else { return }
            guard let password = passwordTextField.text else { return }
            
            //signInWithEmailでログイン
            //第一引数にEmail、第二引数にパスワードを取ります
            FIRAuth.auth()?.signIn(withEmail: email, password: password, completion: { (user, error) in
                //エラーがないことを確認
                if error == nil {
                    if let loginUser = user {
                        // バリデーションが完了しているか確認。完了ならそのままログイン
                        if self.checkUserValidate(user: loginUser) {
                            // 完了済みなら、ListViewControllerに遷移
                            print(FIRAuth.auth()?.currentUser)
                            self.transitionToView()
                        }else {
                            // 完了していない場合は、アラートを表示
                            self.presentValidateAlert()
                        }
                    }
                }else {
                    print("error...\(error?.localizedDescription)")
                }
            })
    }
    
    // ログインした際に、バリデーションが完了しているか返す
    func checkUserValidate(user: FIRUser)  -> Bool {
        return user.isEmailVerified
    }
    // メールのバリデーションが完了していない場合のアラートを表示
    func presentValidateAlert() {
        let alert = UIAlertController(title: "メール認証", message: "メール認証を行ってください", preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
        self.present(alert, animated: true, completion: nil)
    }
```

メールのバリデーションの追加に伴い、SignupViewControllerでのログイン済みかどうかの処理も変更します。

```SignupViewController.swift
    override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)
        //ログインしていれば、遷移
        //FIRAuthがユーザー認証のためのフレーム
        //checkUserVerifyでチェックして、ログイン済みなら画面遷移
        if self.checkUserVerify() {
            self.transitionToView()
        }
    }

    // ログイン済みかどうかと、メールのバリデーションが完了しているか確認
    func checkUserVerify()  -> Bool {
        guard let user = FIRAuth.auth()?.currentUser else { return false }
        return user.emailVerified
    }
```

# 5. Create(データの作成、送信)

では、データの送信を行っていきます。

その前に、今回のFirebaseのデータベースの設計としては、ユーザーのIDにchildとして、投稿をつなげていきます。今回はユーザーは自身の投稿のみを見ることができます。

データベースの設計としては、以下の仕様です。

![Firebase_16.png](https://qiita-image-store.s3.amazonaws.com/0/75302/2baa4814-817b-449b-da48-99bca1915262.png)

Firebaseでは、ルートを以下のコードで指定します。

```
let ref = FIRDatabase.database().reference
```

そこから、childとして枝分かれの部分を指定していきます。

では、まず投稿画面を作ります。
今回は、短文1つのみを投稿できるようにしていますので、TextFieldを追加し、必要なコードを書きましょう。

サンプルプロジェクトでは、ViewController.swiftが投稿のための画面です。

```swift:ViewController.swift
import UIKit
import Firebase //Firebaseをインポート

class ViewController: UIViewController, UITextFieldDelegate {
    
    let ref = FIRDatabase.database().reference() //FirebaseDatabaseのルートを指定
    
    @IBOutlet var textField: UITextField! //投稿のためのTextField
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        textField.delegate = self //デリゲートをセット
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    //投稿ボタン
    @IBAction func post(sender: UIButton) {
            //投稿のためのメソッド
            create()
        
    }
    
    //Returnキーを押すと、キーボードを隠す
    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        textField.resignFirstResponder()
        return true
    }
```
では、最低限必要なコードは記載しましたので、本題のデータを送信するためのcreateメソッドを書きます。

```swift:ViewController.swift
    //データの送信のメソッド
    func create() {
        //textFieldになにも書かれてない場合は、その後の処理をしない
        guard let text = textField.text else { return }
        
        //ロートからログインしているユーザーのIDをchildにしてデータを作成
        //childByAutoId()でユーザーIDの下に、IDを自動生成してその中にデータを入れる
        //setValueでデータを送信する。第一引数に送信したいデータを辞書型で入れる
        //今回は記入内容と一緒にユーザーIDと時間を入れる
        //FIRServerValue.timestamp()で現在時間を取る
        self.ref.child((FIRAuth.auth()?.currentUser?.uid)!).childByAutoId().setValue(["user": (FIRAuth.auth()?.currentUser?.uid)!,"content": text, "date": FIRServerValue.timestamp()])
    }
```

これだけで、CRUDのCreateの部分は完了です。

次に、CRUDのRead(データの受信・読み込み)を行います。

# 6. Read(データの読み込み)
今回はListViewControllerというクラスを作成し、UIViewControllerを継承します。
そこに、TableViewを配置し、先ほどの投稿内容と投稿した時間を表示していきます。

では、TableViewなどの必要なコードを書いていきます。

```swift:ListViewController.swift
import UIKit
import Firebase //Firebaseをインポート

class ListViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
    
    @IBOutlet weak var table: UITableView! //送信したデータを表示するTableView
    
    var contentArray: [FIRDataSnapshot] = [] //Fetchしたデータを入れておく配列、この配列をTableViewで表示

    var snap: FIRDataSnapshot! //FetchしたSnapshotsを格納する変数
    
    let ref = FIRDatabase.database().reference() //Firebaseのルートを宣言しておく
    
    override func viewDidLoad() {
        super.viewDidLoad()
        //データを読み込むためのメソッド
        self.read()
        
        //TableViewCellをNib登録、カスタムクラスを作成
        table.register(UINib(nibName: "ListTableViewCell", bundle: nil), forCellReuseIdentifier: "ListCell")
        
        table.delegate = self //デリゲートをセット
        table.dataSource = self //デリゲートをセット
        
        // Do any additional setup after loading the view.
        
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        //Cellの高さを調節
        table.estimatedRowHeight = 56
        table.rowHeight = UITableViewAutomaticDimension
    }
    
    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        //画面が消えたときに、Firebaseのデータ読み取りのObserverを削除しておく
        ref.removeAllObservers()
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    //ViewControllerへの遷移のボタン
    @IBAction func didSelectAdd() {
        self.transition()
    }

    //ViewControllerへの遷移
    func transition() {
        self.performSegue(withIdentifier: "toView", sender: self)
    }
    
    //セルの数
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return contentArray.count
    }
    
    //返すセルを決める
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        //xibとカスタムクラスで作成したCellのインスタンスを作成
        let cell = table.dequeueReusableCell(withIdentifier: "ListCell") as! ListTableViewCell
        
        return cell
    }   
}

```

必要最低限のTableViewなどのコードが書けたので、データを読み込み表示するためのコードを書きます。

```swift:ListViewController.swift
    func read()  {
        //FIRDataEventTypeを.Valueにすることにより、なにかしらの変化があった時に、実行
        //今回は、childでユーザーIDを指定することで、ユーザーが投稿したデータの一つ上のchildまで指定することになる
        ref.child((FIRAuth.auth()?.currentUser?.uid)!).observe(.value, with: {(snapShots) in
            if snapShots.children.allObjects is [FIRDataSnapshot] {
                print("snapShots.children...\(snapShots.childrenCount)") //いくつのデータがあるかプリント
                
                print("snapShot...\(snapShots)") //読み込んだデータをプリント
                
                self.snap = snapShots
                
            }
            self.reload(snap: self.snap)
        })
    }
    
    //読み込んだデータは最初すべてのデータが一つにまとまっているので、それらを分割して、配列に入れる
    func reload(snap: FIRDataSnapshot) {
        if snap.exists() {
            print(snap)
            //FIRDataSnapshotが存在するか確認
            contentArray.removeAll()
            //1つになっているFIRDataSnapshotを分割し、配列に入れる
            for item in snap.children {
                contentArray.append(item as! FIRDataSnapshot)
            }
            // ローカルのデータベースを更新
            ref.child((FIRAuth.auth()?.currentUser?.uid)!).keepSynced(true)
            //テーブルビューをリロード
            table.reloadData()
        }
    }
```

readとreloadでデータを受信し、配列にいれるところまで処理が完了しました。
ここで、問題としてはtimestampで保存している投稿時間を見やすい形式に変換する必要があるので、そのコードを足します。

```swift:ListViewController.swift
    //timestampで保存されている投稿時間を年月日に表示形式を変換する
    func getDate(number: TimeInterval) -> String {
        let date = Date(timeIntervalSince1970: number)
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy/MM/dd HH:mm"
        return formatter.string(from: date)
    }
```
では次に、Cellに投稿内容と時間を表示していきます。

```swift:ListViewController.swift
    //返すセルを決める
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        //xibとカスタムクラスで作成したCellのインスタンスを作成
        let cell = table.dequeueReusableCell(withIdentifier: "ListCell") as! ListTableViewCell
        
        //配列の該当のデータをitemという定数に代入
        let item = contentArray[indexPath.row]
        //itemの中身を辞書型に変換
        let content = item.value as! Dictionary<String, AnyObject>
        //contentという添字で保存していた投稿内容を表示
        cell.contentLabel.text = String(describing: content["content"]!)
        //dateという添字で保存していた投稿時間をtimeという定数に代入
        let time = content["date"] as! TimeInterval
        //getDate関数を使って、時間をtimestampから年月日に変換して表示
        cell.postDateLabel.text = self.getDate(number: time/1000)
        
        return cell
    }
```

以上で、CRUDのReadまで完了しました。

# 7. Update(データの更新)

次に、ListViewControllerで選択したデータをViewControllerに渡し、投稿内容を変更することができるようにします。

まず、ListViewControllerで選択したCellのデータをViewControllerに渡すためのコードを書きます。

```swift:ListViewController.swift
    //変更したいデータのための変数、CellがタップされるselectedSnapに値が代入される
    var selectedSnap: FIRDataSnapshot!
    //選択されたCellの番号を引数に取り、contentArrayからその番号の値を取り出し、selectedSnapに代入
   //その後遷移
    func didSelectRow(selectedIndexPath indexPath: IndexPath) {
        //ルートからのchildをユーザーのIDに指定
        //ユーザーIDからのchildを選択されたCellのデータのIDに指定
        self.selectedSnap = contentArray[indexPath.row]
        self.transition()
    }
    //Cellがタップされると呼ばれる
    //上記のdidSelectedRowにタップされたCellのIndexPathを渡す
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        self.didSelectRow(selectedIndexPath: indexPath)
    }
    //遷移するときに呼ばれる
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "toView" {
            let view = segue.destination as! ViewController
            if let snap = self.selectedSnap {
                view.selectedSnap = snap
            }
        }
    }
```

続いて、ViewControllerのほうに渡されたデータをもとに更新するための必要なコードを書いていきます。

```swift:ViewController.swift
    var isCreate = true //データの作成か更新かを判定、trueなら作成、falseなら更新
    
    var selectedSnap: FIRDataSnapshot! //ListViewControllerからのデータの受け取りのための変数

    override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)
        //selectedSnapがnilならその後の処理をしない
        guard let snap = self.selectedSnap else { return }
        
        //受け取ったselectedSnapを辞書型に変換
        let item = snap.value as! Dictionary<String, AnyObject>
        //textFieldに受け取ったデータのcontentを表示
        textField.text = item["content"] as? String
        //isCreateをfalseにし、更新するためであることを明示
        isCreate = false
    }
    //Postボタンを以下のように変更
    @IBAction func post(sender: UIButton) {
        if isCreate {
            //投稿のためのメソッド
            create()
        }else {
            //更新するためのメソッド
            update()
        }
        _ = self.navigationController?.popViewController(animated: true)
    }
```
では、本題のupdateメソッドを書いていきます。

```swift:ViewController.swift
    //更新のためのメソッド
    func update() {
        //ルートからのchildをユーザーIDに指定
        //ユーザーIDからのchildを受け取ったデータのIDに指定
        //updateChildValueを使って更新
        ref.keepSynced(true)
        ref.child((FIRAuth.auth()?.currentUser?.uid)!).child("\(self.selectedSnap.key)").updateChildValues(["user": (FIRAuth.auth()?.currentUser?.uid)!,"content": self.textField.text!, "date": FIRServerValue.timestamp()])
    }
```
以上で、CRUDのUpdateまで完了しました。

# 8. Delete(データの削除)
では、CRUDの最後のDelteを行います。

ここでは、ListViewControllerをスワイプすることでDeleteボタンを表示させ、Deleteボタンを押すことで、データの削除を行います。

```swift:ListViewController.swift
    //スワイプ削除のメソッド
    func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
        //デリートボタンを追加
        if editingStyle == .delete {
            //選択されたCellのNSIndexPathを渡し、データをFirebase上から削除するためのメソッド
            self.delete(deleteIndexPath: indexPath)
            //TableView上から削除
            table.deleteRows(at: [indexPath as IndexPath], with: .fade)
        }
    }
```

では、削除のためのメソッドを記載します。

```swift:ListViewController.swift
    func delete(deleteIndexPath indexPath: IndexPath) {
        ref.child((FIRAuth.auth()?.currentUser?.uid)!).child(contentArray[indexPath.row].key).removeValue()
        contentArray.remove(at: indexPath.row)
    }
```

以上でCRUDすべてが完了しました。

## オフラインでもデータにアクセスしたいとき

Firebaseでは、オフラインでもデータにアクセスできるように、ローカルにデータベースを自動で構築する設定があります。

```func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool```のなかで以下の一行を記入すれば大丈夫です

```swift:AppDelegate.swift
FIRDatabase.database().persistenceEnabled = true //ローカルにデータベースを構築する設定
```

また、Firebase上のデータの変化を検知してくれるobserverを有効にした後に、```keeoSynced()```を有効にすれば、ローカルに作成されたデータベースにもデータが反映されます。

keepSyncedの有効に仕方は以下のとおりです

```
// ローカルのデータベースを更新
    ref.child((FIRAuth.auth()?.currentUser?.uid)!).keepSynced(true)
```

# 9. サンプルコード

GitHubにサンプルコードを上げています。
参考にしていただければと思います。

Firebase Sampler
https://github.com/ShinokiRyosei/Firebase-Sampler