🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
Xcode

Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

Q&A

解決済

2回答

2122閲覧

SDWebImageでplaceholderImageを表示した後に画像が追加された場合

abc1222

総合スコア24

Xcode

Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

0グッド

0クリップ

投稿2021/03/23 01:03

アプリのアカウント画像を表示する画面にて、SDWebImageを使用しています。
画像がサーバー上にない場合、placeholderImageを使用して、デフォルト画像を表示しています。
SDWebImageでplaceholderImageを表示した後に画像が追加された場合、追加された画像を反映させるにはどうすればよいのでしょうか。

画像が登録・変更された際、キャッシュを消しているのですが、新しい画像が反映しません。
画像がサーバー上にあり、placeholderImageを使用していない場合には、新しい画像が反映します(他の画面に移動して戻ってきたらですが、、、本当は他の画面に移動しなくても反映してほしい)。

※サーバーにはFirebaseのStorageを利用しています

実際のコード

Swift

1override func viewDidLoad() { 2 super.viewDidLoad() 3 4     accountImageSet(aid: argAid) 5 6} 7 8func accountImageSet(aid: String) { 9 let storageRef = storage.reference() 10 let reference = storageRef.child("account/resize/(aid)_200x200") 11 accountImage.sd_setImage(with: reference, placeholderImage: UIImage(named: "NoImage")) 12} 13 14@IBAction func accountImageTap(_ sender: UITapGestureRecognizer) { 15 let imagePickerController = UIImagePickerController() 16 imagePickerController.sourceType = .photoLibrary 17 imagePickerController.delegate = self 18 self.present(imagePickerController,animated: true,completion: nil) 19} 20 21func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { 22 guard let url = info[.imageURL], let imageUrl = url as? URL else { return } 23 24 let storageRef = storage.reference() 25 let riversRef = storageRef.child("account/(argCid!)")//サーバーでリサイズされて"account/resize/(aid)_200x200"に登録される 26 riversRef.putFile(from: imageUrl, metadata: nil) 27 28 if let cacheKey = accountImage.sd_imageURL?.absoluteString { 29 SDImageCache.shared.removeImageFromMemory(forKey: cacheKey) 30 SDImageCache.shared.removeImageFromDisk(forKey: cacheKey) 31 }else { 32 accountImageSet(aid: argAid!) 33 } 34 35 //フォトライブラリを閉じる 36 picker.dismiss(animated: true) { 37 } 38}

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

自己解決

原因はSDWebImageで一度取得に失敗したreference(URL等)がブラックリストに入ることでした
その結果、新規登録の場合のみ、画像の更新ができなかったようです。
アプリを再起動したら表示できていたので、ブラックリストはアプリを開いている間だけ保存される仕様っぽいですが、そちらは参考サイトを見つけられなかったので、確定ではありません。
下記のように、ブラックリストに保存しないようオプションを設定してあげれば、無事、新規登録の場合でも更新されるようになりました。
オプションを設定する場合、maxImageSizeも指定しないといけないようで、設定数字が妥当なのかわかりませんが、、、

accountImage.sd_setImage(with: reference, maxImageSize: 100000, placeholderImage: UIImage(named: "NoImage"), options: SDWebImageOptions.retryFailed)

投稿2021/04/05 08:09

abc1222

総合スコア24

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

FirebaseのStorageを利用したことはありませんが、パッと見た感じで言うと・・・

画像をサーバにアップロードするのにこんなに短時間で終わるはずがないと思います。
アップロードされてからダウンロードしないと表示されないでしょうね。

投稿2021/03/23 01:25

errolizer

総合スコア441

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

abc1222

2021/03/23 01:38

回答ありがとうございます キャッシュをクリアするタイミングが早いということでしょうか。 画像を変更した際には反映しており、placeholderImageの問題なのかなと思っていたのですが
errolizer

2021/03/23 01:40

他の画面に移動して戻ってきたら、と書いてあったのでそう思いました。 試しにアップロードしてキャッシュをクリアしてから、0.5秒後に ダウンロードしてみてはいかがですか?
errolizer

2021/03/23 01:42

というか、今気づいたのですが、画像をアップロードしたあと、ダウンロードしてないように見えますよ。 accountImageSet(aid: argAid!)呼んでいますか?
errolizer

2021/03/23 01:46

具体的には、 } else { accountImageSet(aid: argAid!) } を下記に変更しないと画像が表示されないはずです。 } accountImageSet(aid: argAid!)
abc1222

2021/03/23 02:02

申し訳ありません。 質問する上でややこしくなるかなと思って抜いてしまっていたのですが、下記のように見た目上は即反映のようにしています。 } guard let image: UIImage = info[.originalImage] as? UIImage else { return } accountImage.image = image ただ、上記でも、変更の場合は変更後すぐに他のページに移動して戻ってきた際に画像が反映、新規登録の場合、一拍おいてページを移動して戻ってきても反映していない状態です。
errolizer

2021/03/23 02:16

私の方も想像だけで話してしまってます。すいません。 公式を見ると、アップロードの監視方法が書いてありました。 https://firebase.google.com/docs/storage/ios/upload-files?hl=ja これをコピペして、ログで「アップロード完了!」とか吐かせてみてはいかがでしょうか?
abc1222

2021/03/23 03:34

ありがとうございます! 公式サイトに書いてあったのですね。お恥ずかしい。 サーバー側でリサイズしているのでうまくいくかわかりませんが、キャッシュクリアを少し遅らせたりできそうなので、上記を元に色々試してみます
errolizer

2021/03/23 03:38

やってみました。リサイズしない場合はこれでいけましたよ。 確かにリサイズにも時間かかるのでちょっと工夫が必要そうですね・・・ if let cacheKey = imageView.sd_imageURL?.absoluteString { SDImageCache.shared.removeImageFromMemory(forKey: cacheKey) SDImageCache.shared.removeImageFromDisk(forKey: cacheKey) } riversRef.putFile(from: imageUrl, metadata: nil) { (_, error) in if let e = error { print("error (e.localizedDescription)") } else { print("upload success") self.accountImageSet(aid: xxxx) // アップロード完了/成功したので画像を読みこむ } } accountImageSet(aid: xxxx) // ここではまだアップロードが完了していないのでダメ
errolizer

2021/03/23 03:49

Firebaseのリサイズ機能をざっと見てみましたが、ユーザのプロフィールアイコンのリサイズが目的であれば、ローカルでリサイズしてアップロードする方がいろんな意味で良いと思いました。 そのほうが簡単でシンプルかと・・・余計なことかもしれませんが。 ネットワークリソース、ユーザの通信費用、Firebase利用料金・・・
abc1222

2021/03/24 02:36

確認が遅くなり申し訳ありません。 上記を参考にしながら、リサイズ完了の通知を受け取れたため、とりあえずサーバー側でのリサイズのままで触ってみました。 その中で、やはりSDWebImageの仕様が掴みきれないでいます。 ・リサイズ完了を受け取り、キャッシュのクリアと画像の読み込みを行う →画像の変更の場合は新しい画像が表示されるが、新規登録の場合placeholderImageが表示されるという仕様のまま 一度placeholderImageを表示すると、変更できないのでしょうか
errolizer

2021/03/24 03:50 編集

画像は読み込んでいますか? 上記のコードをみる限り、placeholderを設定している場合は画像を読み込んでいないように見えます。 最新のソースコードを載せていただければ・・・ SDWebImageのplaceHolderは画像を受信するまでに表示する画像のことです。 SDWebImageの最新版を利用し、ドキュメント通りに実装してみてください。
errolizer

2021/03/24 04:34

Firebaseのドキュメントを見てきましたが「リサイズ完了の通知を受け取る方法」は公式には用意されていませんでした。 どのように実装されたのでしょうか? また、このリサイズ機能は有料サービス(使用しなくても従量課金)ですので、私の方では試せません。 ここまでの話を総合的にみると、単純に存在しない画像を取得しようとしてplaceHolderが表示されているのだと推測します。 iPhoneの写真は12MP程度あります。これをアップロードしてサーバでリサイズするのはかなりの重い作業です(だから有料です) スマホのボタンを押してすぐに終わるようなものではありません。 技術選定から慎重に検討することをお勧めします。
abc1222

2021/03/24 04:56

コード失礼しました。 変更点としては、画像変更時にキャッシュをクリアし、リサイズが終わった後に画像を設置するようにしています。 ログを確認し、リサイズ後にaccountImageSetが作動していることも確認はしています。 しかし、下記のコードで、画像変更時は更新されるが、新規登録時は更新されないという問題がおきます。 新規登録の場合はplaceHolderがずっと画像を探していて、新たにsd_setImageをしても受け付けてもらえないのでしょうか。 override func viewDidLoad() { super.viewDidLoad()    accountImageSet(aid: argAid!) } func accountImageSet(aid: String) { let storageRef = storage.reference() let reference = storageRef.child("account/resize/(aid)_200x200") accountImage.sd_setImage(with: reference, placeholderImage: UIImage(named: "NoImage")) } func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { guard let url = info[.imageURL], let imageUrl = url as? URL else { return } let storageRef = storage.reference() let riversRef = storageRef.child("account/(argAid!)") riversRef.putFile(from: imageUrl, metadata: nil) if let cacheKey = circleImage.sd_imageURL?.absoluteString { SDImageCache.shared.removeImageFromMemory(forKey: cacheKey) SDImageCache.shared.removeImageFromDisk(forKey: cacheKey) } //フォトライブラリを閉じる picker.dismiss(animated: true) { } } @objc func accountChange(_ notification: Notification) { accountImageSet(aid: argAid!) } ■補足 最初はキャッシュクリアもリサイズが終わった後に設定したのですが、うまく行かず下記の様になりました。 うまく行かなかった点 ・他のページでもアカウント画像を表示しているが、そちらの画像が更新されなかった ・他のページに移動した後にアカウントページに戻ると、アカウントページのアカウント画像は更新されていた
abc1222

2021/03/24 04:59

入れ違いになってしまいました。 リサイズの完了は、サーバーでリサイズした際にデータベース(Firebase)のアカウント情報(更新日)を更新しており、そこが変更された際にリスナーで作動するようにしています。
errolizer

2021/03/24 05:57

circleImageとは何でしょう? Firebaseの更新時刻が変更になった時に通知が来るとは面白いですね。すべてのユーザに通知されたりしないように制御してるということでしょうか。
errolizer

2021/03/24 06:06

「更新されなかった」「更新されていた」のように画面上の結果だけを追うのではなく、 - リクエストを行った or 行わなかった - その結果は成功だった or 失敗だった のように処理の結果を追うようにすれば解決に近づくと思います。 SDWebImageのAPIで結果が補足できます。 例) imageView.sd_setImage(with: reference, placeholderImage: image) { image, error, type, reference in   if let e = error {     print(e.localizedDescription)   } else {     print("通信成功")   } }
abc1222

2021/03/24 06:29

失礼しました。circleImage→accountImageですね。。。ここに書き写すときにミスしたので、実際のコード側のミスではありません。 Firebaseでは、特定のデータ階層?(Firebaseではコレクション・ドキュメントと呼ぶみたいです)のみに変更のリスナーが設置できるので、該当のアカウントのドキュメントに対してリスナーを設置しています。 記載いただいたエラーの出し方を丁度調べていて、自分のコードでは下記のようなエラーが出ていることがわかりました。 Error Domain=SDWebImageErrorDomain Code=1003 "Image url is blacklisted" UserInfo={NSLocalizedDescription=Image url is blacklisted} リサイズが終わってもすぐにURLが発行されているわけではないのかもしれません もう少し調べてみないとエラーの意味が正確にわからないのですが、、、 調べてもサイトに書いてあることがわからず、時間がかかってしまっていますが、おかげさまでちょっとずつ解決に近づいているような気がします
abc1222

2021/03/25 08:38

おかげさまで解決しました! 原因はSDWebImageで一度取得に失敗したreference(URL等)がブラックリストに入ることでした その結果、新規登録の場合のみ、画像の更新ができなかったようです。 アプリを再起動したら表示できていたので、ブラックリストはアプリを開いている間だけ保存される仕様っぽいですが、そちらはサイトを見つけられなかったので、確定ではありません。 下記のように、ブラックリストに保存しないようオプションを設定してあげれば、無事、新規登録の場合でも更新されるようになりました。 エラーログの見方を教えていただいたおかげです。ありがとうございます! オプションを設定する場合、maxImageSizeも指定しないといけないようで、設定数字が妥当なのかわかりませんが、、、 accountImage.sd_setImage(with: reference, maxImageSize: 100000, placeholderImage: UIImage(named: "NoImage"), options: SDWebImageOptions.retryFailed)
abc1222

2021/03/25 08:41

色々教えていただいたのでベストアンサーにしたいのですが、上記のコードを回答で入れていただければ選びます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問