iOSエンジニアのつぶやき

毎朝8:30に iOS 関連の技術について1つぶやいています。まれに釣りについてつぶやく可能性があります。

【Swift】Firebase Storage で JPEG ファイルをアップロードする

今日はタイトルの通り、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

また、StorageUploadTaskobserve というメソッドを使用することでアップロードタスクのイベントを受け取ることもできます。下記のサンプルでは引数(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 を指定しています。これにより下記のようにコンソールで画像が表示されるようになります。

f:id:yum_fishing:20201211155531p:plain

let meta = StorageMetadata()
meta.contentType = "image/jpeg"

この他にも多くのメタデータを付与することができるので、詳しくは下記を参照してみてください🧑‍🔧

Firebase.Storage.StorageMetadata Class Reference

困ったこと

今回の実装を行った上で困ったことは、FirebaseUI による画像のキャッシュの問題です。

FirebaseUI とは、その名の通り Firebase の様々な機能をより簡単に扱えるようにするために Firebase から提供されている iOS SDK で、これを使用することで開発者は素早く Firebsae による UI 層の実装を行うことができます。

github.com

今回はその中でも 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"

また、なにかいい方法が記事を書こうと思います。(いい方法を知っている方がいれば教えてください✨)

参考

その他の記事

yamato8010.hatenablog.com

yamato8010.hatenablog.com

yamato8010.hatenablog.com