10
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Swift 向け Firebase RealtimeDB+Storage の画像読み込みライブラリを作った

Posted at

最近Firebaseをバックエンドにしたアプリをよく開発しているのですが RealtimeDB, Storage, TableView で問題にぶち当たったので解決のためにライブラリを作りました。
Firebase RealtimeDB, Storage に関しては Salada を利用している前提です。

Firebase RealtimeDB+Storage よる画像読み込みの問題

TableViewCell + ImageView で以下のような問題が発生

  • cellForRowAtIndexPath で Model を取得
  • ImageView に画像を load
  • 読み込み終了前にスクロール
  • cell が reuse される
  • 次の indexPath で別の画像を load
  • 最初の画像が一瞬表示され、その後に適切な画像が表示される
class MyTableViewCell {
    @IBOutlet weak var imageView: UIImageView!

    func configure(id: String) {
        Firebase.User.observeSingle(id: id, type: .value) { user in
            guard let user: Firebase.User = user else { return }
            if let profileImageRef = user.profileImage.ref {
                imageView.sd_setImage(ref)
            }
        }
    }

    func prepareForReuse() {
        imageView.image = nil
    }
}

この問題はセル内での2重の非同期通信によって発生し、以下のような図で表せます。

download.png

解決策

ここで発生している問題は以下の2点です。

  • cell の reuse 時に画像ダウンロードをキャンセルしていない
  • imageViewの image を set する時に、正しい画像かどうかを判定していない

まず、cell の reuse 時に画像ダウンロードをキャンセルしていないから解決していきます。
これは UIImageView の extention に cancelLoading() suspendLoading() を実装しました。
これらのメソッドのどちらかを呼ぶことで画像の読み込みを停止、キャンセルできます。
次に imageView の image を set する時に、正しい画像かどうかを判定していない に関しては load 時に Bool を返り値に取るブロックを渡すことで、その条件を満たした場合のみ画像を set するようにするメソッド load(_ storageReference: StorageReference, shouldSetImageConditionBlock: @escaping (() -> Bool) = { return true } ) を UIImageView の extension に実装しました。

これらによって最終的なセルの実装はこんな感じになります。

class MyTableViewCell {
    @IBOutlet weak var imageView: UIImageView!
    private var userID: String?

    func configure(id: String) {
        Firebase.User.observeSingle(id: id, type: .value) { [weak self] user in
            guard let `self` = self else { return }
            guard let user: Firebase.User = user else { return }
            if let profileImageRef = user.profileImage.ref {
                imageView.load(profileImageRef) { self.userID == user.ID }
            }
        }
    }

    override func prepareForReuse() {
        super.prepareForReuse()
        userID = nil
        imageView.suspendLoading()
        imageView.image = nil
    }
}

ここまでやれば非同期の重なる TableViewCell での画像読み込みで古い画像でチラついたりすることはなくなります。ヤッター。

今回実装したライブラリ

ここにあります

pod 'ImageStore'

して使ってください。
FirebaseStorage は Pod の dependency に入れられないので Example/ImageStore/ 以下にある UIImageView+FirebaseStorage.swift ImageStore+FirebaseStorage.swift をプロジェクトに入れると幸せになれます。
あとは README.md とこの記事を読めばなんとなく使い方が分かると思います。
またバグ等ありましたらぜひ issue や PR 投げていただけると幸いです。

10
5
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?