【Swift5】FirestoreとFirebase Storageでの画像の扱い方(初学者向け)

# 背景
Firebaseを使用して開発することが多く、画像をどのように扱うかを毎回忘れてしまうので
記事に残すことにした。

# 画像の扱い方について
Firebaseを用いて画像を扱う場合によく使われる方法が2つあります。

## ①Storageから直接取得する

### 保存時の流れ
1. Storageに画像を保存 

### 取得時の流れ
1. Storage Refを使用して画像を取得する

### メリット
- Storageにセキュリティールールを設定できるので、セキュリティが高くなる
- Storage Refで取得できるので実装が簡単にできる

### デメリット
- 画像の一括取得ができないので、リスト表示などをする際は時間がかかってしまう


## ②DownloadURLをFirestoreへ保存する
### 保存時の流れ
 1. Storageに画像を保存 
 2. 保存した画像のdownloadURLを取得
 3. FirestoreへdownloadURLを保存

### 取得時の流れ
 4. FirestoreよりdownloadURLを取得する 

### メリット
- Firestoreから取得を行うので、一括取得ができる(リスト表示しやすい)
- URLなのでキャッシュできる

### デメリット
- URLがわかれば誰でもアクセスできるようになる
- 保存時に何度か通信が行われるのでエラーハンドリングなど含めると実装がすこし複雑になる

今回は②の方の実装について書こうと思います。(エラーハンドリングは除く)

# 実装

## 保存時の処理
コメントを入れて解説してるので、少し長くなっていますが
一連の流れは以下になります。

```PostViewController.swift

class PostViewController: UIViewController {

// 省略
    
    func saveToFirestore() {
        // nilチェック
        if let title = titleTextField.text,
            let content = contentTextField.text,
            let selectImage = imageView.image {
            // 今日日付をintに変換して被らない名前にする
            let imageName = "\(Date().timeIntervalSince1970).jpg"
            // 今回はpostsというフォルダーの中に画像を保存する
            let reference = Storage.storage().reference().child("posts/\(imageName)")
            // 画像データがそのままだとサイズが大きかったりするので、サイズを調整
            if let imageData = selectImage.jpegData(compressionQuality: 0.8) {
                // メタデータを設定
                let metadata = StorageMetadata()
                metadata.contentType = "image/jpeg"
                // ①ここでstorageへの保存を行う
                reference.putData(imageData, metadata: metadata, completion:{(metadata, error) in
                    if let _ = metadata {
                    // ②storageへの保存が成功した場合はdownloadURLの取得を行う
                    reference.downloadURL{(url,error) in
                            if let downloadUrl = url {
                                // downloadURLの取得が成功した場合
                                // String型へ変換を行う
                                let downloadUrlStr = downloadUrl.absoluteString
                                // ③firestoreへ保存を行う
                                Firestore.firestore().collection("posts").document().setData([
                                    "title": title,
                                    "content": content,
                                    "imageURL": downloadUrlStr,
                                    "createdAt": FieldValue.serverTimestamp()
                                ]){ error in
                                    if let error = error {
                                        // firestoreへ保存が失敗した場合
                                        
                                    } else {
                                        // firestoreへ保存が成功した場合
                                    }
                                }
                            } else {
                                // downloadURLの取得が失敗した場合の処理
                            }
                        }
                    } else {
                        // storageの保存が失敗した場合の処理
                    }
                })
            }
        }
    }
}

```

上記のように３回非同期の処理が入るので
エラーハンドリングを実装する必要があります。(今回は割愛します)


## 取得時の処理
投稿データのモデルを以下のように定義

```PostData.swift
class PostData: NSObject{
    var id: String
    var title: String?
    var content: String?
    var imageURL: String?
    var date: Date?

    init(document: QueryDocumentSnapshot) {
        self.id = document.documentID

        let postDic = document.data()

        self.title = postDic["title"] as? String

        self.content = postDic["content"] as? String
        
        self.imageURL = postDic["imageURL"] as? String

        let timestamp = postDic["createdAt"] as? Timestamp
        self.date = timestamp?.dateValue()
    }
}
```



```ListViewController.swift

class ListViewController: UIViewController, UITableViewDelegate,UITableViewDataSource {
    
    var listData:[PostData] = []

    override func viewDidLoad() {
        super.viewDidLoad()
        // ④Firestoreからデータを取得
        Firestore.firestore().collection("posts").getDocuments{ QuerySnapshot, error in
            if let snapshot = QuerySnapshot {
                listData = snapshot.documents.map { document in
                    let postData = PostData(document: document)
                    return postData
                }
                // tableViewなどに表示する場合はここでreloadDataを呼ぶ
            }
        }
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return listData.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        // 画像を表示する場合
        // stringからURL型に変換
        let imageUrl:URL = URL(string: listData[indexPath.row].imageURL as! String)!
        // URL型からData型に変換
        let imageData:Data = try! Data(contentsOf: imageUrl)
        // 画像をセットする
        cell.imageView.image = UIImage(data: imageData)!

        return cell
    }
}
```

上記のようにFirestoreからURLを一括取得できるので、かなり楽にリスト表示をすることができます。