背景
Firebaseを使用して開発することが多く、画像をどのように扱うかを毎回忘れてしまうので
記事に残すことにした。
画像の扱い方について
Firebaseを用いて画像を扱う場合によく使われる方法が2つあります。
①Storageから直接取得する
保存時の流れ
- Storageに画像を保存
取得時の流れ
- Storage Refを使用して画像を取得する
メリット
- Storageにセキュリティールールを設定できるので、セキュリティが高くなる
- Storage Refで取得できるので実装が簡単にできる
デメリット
- 画像の一括取得ができないので、リスト表示などをする際は時間がかかってしまう
②DownloadURLをFirestoreへ保存する
保存時の流れ
- Storageに画像を保存
- 保存した画像のdownloadURLを取得
- FirestoreへdownloadURLを保存
取得時の流れ
- 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の保存が失敗した場合の処理
}
})
}
}
}
}
上記のように3回非同期の処理が入るので
エラーハンドリングを実装する必要があります。(今回は割愛します)
取得時の処理
投稿データのモデルを以下のように定義
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を一括取得できるので、かなり楽にリスト表示をすることができます。