今日はタイトルの通り、Firebase Storage で JPEG ファイルを Swift でアップロードする方法を紹介したいと思います👷♀️
使ったもの
今回の実装をする上で使用したものは下記になります。
Firebase/Storage 6.34.0
RxSwift 6.0.0-rc.1
Xcode12.0.1(Swift5.3)
それではやっていく
ということで JPEG ファイルをアップロードするためのメソッドが下記のようになります。
func updateProfileImage(uid: String, jpegData: Data) -> Observable<String> { return Observable<String>.create { observer -> Disposable in let today = Date().convertToTodayString() // e.g) 20201211031942 let path = "users/\(uid)/profile_\(today).jpg" let meta = StorageMetadata() meta.contentType = "image/jpeg" let uploadTask = Storage.storage().reference(withPath: path).putData(jpegData, metadata: meta) { _, error in if let error = error { observer.onError(error) } else { observer.onNext(path) observer.onCompleted() } } return Disposables.create { uploadTask.cancel() } } }
こちらは、個人開発しているアプリの実装をサンプルで用いていて、アプリにはクリーンアーキテクチャを採用しているので、なにかと Observable
に包んだ実装になっています。また、今回は画像ファイルのアップロードを行っていますが、この他にも音声や動画などのファイルを Firebase Storage で使用することができます。
putData
または putFile
では StorageUploadTask
というオブジェクトが返され、それらを使用して下記のようにアップロードを管理することができます。今回の実装では、Observable
の購読が解除されるタイミングで uploadTask.cancel()
を実行し、アップロードの中断を行っています。
// Start uploading a file let uploadTask = storageRef.putFile(from: localFile) // Pause the upload uploadTask.pause() // Resume the upload uploadTask.resume() // Cancel the upload uploadTask.cancel()
参照: iOS でファイルをアップロードする | Firebase
また、StorageUploadTask
の observe
というメソッドを使用することでアップロードタスクのイベントを受け取ることもできます。下記のサンプルでは引数(StorageTaskStatus
)に .progress
を指定することでアップロードタスクの進捗をイベントとして受け取ることができます。
uploadTask.observe(.progress) { snapshot in // Upload reported progress let percentComplete = 100.0 * Double(snapshot.progress!.completedUnitCount) / Double(snapshot.progress!.totalUnitCount) }
その他の StorageTaskStatus
については下記を参照してください。
iOS でファイルをアップロードする | Firebase
これらの Observer は下記のようにして明示的に削除することも可能ですが、アップロードタスクが完了するまでは削除しないという場合は、タスクが成功または失敗したタイミングで Observer は自動的に削除されます。
// Create a task listener handle let observer = uploadTask.observe(.progress) { snapshot in // A progress event occurred } // Remove an individual observer uploadTask.removeObserver(withHandle: observer)
putData
に渡しているメタデータですが、今回の場合は Firebase コンソールでより簡単にプレビューを行うために contentType
を指定しています。これにより下記のようにコンソールで画像が表示されるようになります。
let meta = StorageMetadata() meta.contentType = "image/jpeg"
この他にも多くのメタデータを付与することができるので、詳しくは下記を参照してみてください🧑🔧
Firebase.Storage.StorageMetadata Class Reference
困ったこと
今回の実装を行った上で困ったことは、FirebaseUI
による画像のキャッシュの問題です。
FirebaseUI
とは、その名の通り Firebase の様々な機能をより簡単に扱えるようにするために Firebase から提供されている iOS SDK で、これを使用することで開発者は素早く Firebsae による UI 層の実装を行うことができます。
今回はその中でも SDWebImage
というものを使用して、画像が保存されている Storage Path から UIImageView
に非同期で画像を取得して表示する処理を行っていました。
let ref = Storage.storage().reference(withPath: storagePath) imageView.sd_setImage(with: ref, placeholderImage: placeholderImage)
ただ、この SDWebImage
は渡された Storage Path と取得した画像を Key-Path
形式でキャッシュするので、特定の Storage Path にあるファイルをアップデートした際にも、新しいファイルは読み込まれず前回のキャッシュから取得してきて表示されるようになってしまいました。アップデートのタイミングで明示的にキャッシュを削除することもできるのですが、その場合他のデバイスでも同様にキャッシュの削除を行うことが困難なため、前述した通り、Storage Path のプレフィックスに日時データを追加することで解決しました。ただ、この場合だと不要なファイルを削除したい場合は、ファイルのアップデートのタイミングで前回のファイルを削除してからアップロードを行う必要があるので一手間増えてしまいます。
let today = Date().convertToTodayString() // e.g) 20201211031942 let path = "users/\(uid)/profile_\(today).jpg"
また、なにかいい方法が記事を書こうと思います。(いい方法を知っている方がいれば教えてください✨)