Swift + Firebaseで簡単なiOSアプリを作成してみよう

# 環境
* OS：MacOS Catalina 10.15.1
* Swift 5.1
* Xcode 11.2.1

# 作成するアプリのイメージ

ユーザーを管理するアプリを作成します。
理由としては、以下の3点。

* 自分で定義した型を使う
* CRUDが全部できる
* かんたん

ユーザーである必要はないんだけど、型が考えやすいので。

# Firebaseの環境設定

環境設定に関しては、下記の記事を参照してください。
[iOSでFirebaseを使用して、データベースに対してCRUDを試してみる。 - Qiita](https://qiita.com/hajime_manul/items/c0cc51c06934a3162e77)

# 一覧画面を作成しよう

## ユーザー型の定義

ユーザーは、以下の要素を持つことにします。

```swift:User.swift
import UIKit
import Firebase

struct User {
    var name: String = ""
    var age: Int = 0
    var favoriteFood: String = ""
}
```

## StoryBoardを作成する

画像のように、NavigationControllerとTableView、TableCellを配置します。
TableCellにIDをつけておきましょう。(今回はUserTableCellにしました)

![0f63476b.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/418332/fc1e98fb-262e-6a05-9496-d7d17f156040.png)

TableCellに3つラベルを配置します。後で使うのでTagもつけておきます。

![65647859.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/418332/e4572476-acc8-13e9-efec-d5b34d1adfd1.png)

## ViewControllerを作成する

Firebaseからの応答は、直接構造体に格納できません。そこで、[String: Any]から自分に代入するメソッドを追加します。

```swift:User.swift
import UIKit
import Firebase

struct User {
    var name: String = ""
    var age: Int = 0
    var favoriteFood: String = ""
    
    /// Dictionaryから、自分自身に代入します。
    /// - Parameter dictionary: User型
    mutating func setFromDictionary(_ dictionary: [String: Any]) {
        name = dictionary["name"] as? String ?? ""
        age = dictionary["age"] as? Int ?? 0
        favoriteFood = dictionary["favoriteFood"] as? String ?? ""
    }
}
```

TableViewに取得したユーザーを表示するプログラムを書きます。

```swift:UsersListViewController.swift
import UIKit
import Firebase

class UsersListViewController: UIViewController, UITableViewDataSource, UITableViewDelegate  {
    
    @IBOutlet weak var usersTableView: UITableView!
    var users: [User]!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        usersTableView.dataSource = self
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        let ref = Database.database().reference()
        
        ref.child("Users").observe(.value) { (snapshot) in
            // 初期化
            self.users = []
            
            for data in snapshot.children {
                let snapData = data as! DataSnapshot
                let dictionarySnapData = snapData.value as! [String: Any]
                
                var user = User()
                // 取得した内容をユーザー型にセット
                user.setFromDictionary(dictionarySnapData)
                
                // ユーザーリストに追加
                self.users.append(user)
            }
            
            // すべてユーザーリストに格納したら、TableViewを更新する。
            self.usersTableView.reloadData()
        }
    }
    
    // MARK: - TableViewMethod
    // セルの個数
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        guard let _users = users else { return 0 }
        return _users.count
    }
    // セルに値をセット
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        let cell = usersTableView.dequeueReusableCell(withIdentifier: "UserTableCell", for: indexPath)
        
        let user = users[indexPath.row]
        
        let nameLabel = cell.viewWithTag(1) as! UILabel
        nameLabel.text = user.name
        
        let ageLabel = cell.viewWithTag(2) as! UILabel
        ageLabel.text = String(user.age)
        
        let favoriteLabel = cell.viewWithTag(3) as! UILabel
        favoriteLabel.text = user.favoriteFood
        
        return cell
    }
    // セルタップ時
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath as IndexPath, animated: true)
    }
    
}

```

![652a9db2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/418332/24933998-a8a8-7097-f002-8387b7582783.png)

# 編集画面を作成しよう

## StoryBoardを作成する
新しいViewControllerを配置して、TextField3つとButtonを1つ追加します。
あと一覧画面に遷移用のボタンを追加しておきます。

![831c43b3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/418332/76641b83-9618-5caa-6684-2fc419d6d4f1.png)

## ViewControllerを作成する

Dictionaryでやり取りするので、Userに以下のメソッドを追加します。

```swift:User.swift
/// Dictionaryに変換します。
var toDictionary: [String: Any] {
    return [
        "name": name,
        "age": age,
        "favoriteFood": favoriteFood
    ]
}
```

追加用の処理を書きます。

``` swift:UserEditViewController.swift

import UIKit
import Firebase

class UsersEditViewController: UIViewController {

    @IBOutlet weak var nameText: UITextField!
    @IBOutlet weak var ageText: UITextField!
    @IBOutlet weak var favoriteFoodText: UITextField!
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
    }
    
    @IBAction func submitButtonTapped(_ sender: UIButton) {
        
        let ref = Database.database().reference()
        
        // 入力内容をUserインスタンスに入れる
        var newUser = User()
        newUser.name = nameText.text!
        newUser.age = Int(ageText.text!) ?? 0
        newUser.favoriteFood = favoriteFood.text!
        
        // データを登録
        ref.child("Users").childByAutoId().setValue(newUser.toDictionary)
        
        // 登録後 前画面に戻る
        self.navigationController?.popViewController(animated: true)
    }
}

```
実行結果

![b1ce87b4.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/418332/8602426d-59c7-9129-4326-45fa214df74f.gif)

## 更新モードを作成しよう

前画面からキーを受け取れるようにし、
キーを指定して、データに値をセットします。
(下記以外にも色々追加していますが、詳細については最後に貼ります。)

```swift:UserEditViewController.swift

import UIKit
import Firebase

class UsersEditViewController: UIViewController {

    @IBOutlet weak var nameText: UITextField!
    @IBOutlet weak var ageText: UITextField!
    @IBOutlet weak var favoriteFoodText: UITextField!
    @IBOutlet weak var submitButton: UIButton!
    
    var user: User!
    // 前画面からキーを受け取る
    var key: String!
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        // 前画面から受け取った値をセットする
        // (ホントはキーのみ受け取って再検索するほうが良さそう)
        nameText.text = user.name
        ageText.text = String(user.age)
        favoriteFoodText.text = user.favoriteFood
    }
    
    // MARK: - EventFunctions
    @IBAction func submitButtonTapped(_ sender: UIButton) {
        
        let ref = Database.database().reference()
        
        // 入力内容をUserインスタンスに入れる
        var newUser = User()
        newUser.name = nameText.text!
        newUser.age = Int(ageText.text!) ?? 0
        newUser.favoriteFood = favoriteFoodText.text!
        

        // データを更新
        ref.child("Users").child(key).setValue(newUser.toDictionary)
        
        // 処理終了後 前画面に戻る
        self.navigationController?.popViewController(animated: true)
    }

```

![3e94725e.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/418332/d559383b-4127-55cf-1210-ee6de63f9067.gif)

## 削除機能を作成しよう

スワイプ時のTableViewMethodを追加します。

```swift
// セルスワイプ時(削除)
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) ->
UISwipeActionsConfiguration? {
    
    let delete = UIContextualAction(style: .destructive, title: "削除") { (action, sourceView, completionHandler) in
completionHandler(true)
        
        let ref = Database.database().reference()
        ref.child("Users").child(self.keys[indexPath.row]).removeValue()
    }
    let swipeAction = UISwipeActionsConfiguration(actions: [delete])
    swipeAction.performsFirstActionWithFullSwipe = true
    return swipeAction
}

```

![9ad8c400.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/418332/bc68ff64-1446-4b4a-8c13-dcb775a7a325.gif)

# ソースコード
ソースコードはGitHubに置いてますので、欲しい人はどうぞ。
[SampleFirebase](https://github.com/Hajime3778/SampleFirebase)

# まとめ

とりあえず基本のCRUDをアプリっぽく再現できたぞ！
今回はRealTimeDatabaseを使用しましたが、CloudFireStoreのほうが今はおすすめらしいので、そっちも時間があればやってみます。