こんにちは。iOS アプリ開発担当の ono(@ono)です。
こちらの記事は Gunosy Tech Blog Festa の 6 日目の記事です。
UIHostingConfiguration を UICollectionView で使用し AsyncImage で画像を表示した際、セルの再表示時にチラつく問題が発生しました。本記事では、この問題を回避する方法をご紹介します。

はじめに
iOS 16 で導入された UIHostingConfiguration は、SwiftUI のビューを UIKit のセルに組み込むための強力な API です。しかし、AsyncImage と併用する際には「チラつき」という事象に直面することがありました。
今回は UHHostingConfiguration を導入する際に発生したチラつきの事象に対しての検証と改善策の紹介します。
取り扱う問題について説明
UICollectionView の中で UIHostingConfiguration を導入し、API から取得した画像を AsyncImage で表示しています。その際に、セルを押下したり要素を更新したりすると、View が再読み込みされて画像の再表示時に読み込みが発生します。この読み込みによるチラつき問題について説明します。
チラつかなくする方法
結論として、UIHostingConfiguration 内部の View にて AsyncImage を用いて画像を表示することがチラつきの原因でした。 下記のように View に id を設定し同じデータの場合の表示更新を抑止することと、AsyncImage ではなくキャッシュ機能を持つ サードパーティライブラリの SDWebImage を用いるようにしてみると解決しました。
let cell = collectionView.dequeueReusableCell(with: ContentViewCell.self, for: indexPath) cell.contentConfiguration = UIHostingConfiguration { WebImage(url: URL(string: image.url)) { image in image .resizable() .aspectRatio(contentMode: .fill) } placeholder: { Rectangle() .fill(Color(UIColor.systemGray5)) } .clipped() .frame(width: itemSize.width, height: itemSize.height) .id(coupon.id) } .margins(.all, 0)
- View に id を設定する
- キャッシュ機能のある画像表示ライブラリ ( SDWebImage ) 使う
チラつく理由の検証・考察
結論に至るまでの調査 / 過程を紹介します。
1. Diffable Data Souce の活用
原因は UIHostingConfiguration にしたことで、reladData() する際に SwiftUI の View が再生成されて再読み込みが発生していると考えました。通常の UIKit では reloadData() を呼びだしても、UICollectionView はセルのインスタンスを再利用します。これは参照型ベースの仕組みで、既存のセルオブジェクトの内容を更新するだけなので、チラつきは発生しないと考えています。一方で、UIHostingConfiguration は値型です。セルに対して新しい contentConfiguration をセットするたびに再生成が発生すると考えました。
internal func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(with: ContentViewCell.self, for: indexPath) // これを実行するたびにチラつくと予想 cell.contentConfiguration = UIHostingConfiguration { AsyncImage(url: URL(string: Image.url)) { image in image .resizable() .aspectRatio(contentMode: .fill) } placeholder: { Rectangle() .fill(Color(UIColor.systemGray5)) } .clipped() .frame(width: itemSize.width, height: itemSize.height) } return cell }
更新のないセルに対しては変更させない実装にしたら良いのではと考え以下のように実装してみました。 Diffable Data Souce の導入です。Diffable Data Source が差分を計算して、追加 / 削除 / 移動されたセルのみを更新させるようにしてみました。
// -- 他の処理は割愛 -- // 更新処理 var snapshot = NSDiffableDataSourceSnapshot<SectionKind, ItemIdentifier>() // ... スナップショット作成 ... diffableDataSource.apply(snapshot) // ← ここで差分計算
結果としては差分のないセル 関してはチラつきは発生しませんでした。しかし、差分のあるセルにはチラつきが発生しました。diffableDataSource.apply(snapshot, animatingDifferences: true)のようにアニメーションをつける場合であればセル移動は既存のセルが使われたりするため視覚的には軽減できます。
2. UIHostingConfiguration を初期化時に定義し使い回す
セルの更新のたびに UIHostingConfiguration が生成されていることがチラつきの原因と考えました。
UIHostingConfiguration のセルの初期化時に定義し、使い回しを行うようにしてみました。各種プロパティに関しては configure で変更を加えるようにしています。
internal final class ContentViewCell: UICollectionViewCell { override internal init(frame: CGRect) { super.init(frame: frame) setup() } internal func setup() { contentConfiguration = UIHostingConfiguration { AsyncImage(url: URL(string: Image.url)) { image in image .resizable() .aspectRatio(contentMode: .fill) } placeholder: { Rectangle() .fill(Color(UIColor.systemGray5)) } .clipped() .frame(width: itemSize.width, height: itemSize.height) } .margins(.all, 0) } internal func configure(someProperty: SomeProperty) { self.someProperty = someProperty } }
UIHostingConfiguration の再生成は起こらず、各種プロパティの更新のみにできましたが、チラつきは発生しました。UIHostingConfiguration の再生成が原因ではないようです。
AsyncImage に対して id を付与してみる
画像の読み込みによるチラつきによると考え、View に対して id を付与し、再度同じ画像が表示された時に読み込まないようにしました。
AsyncImage(url: URL(string: imageUrl)) { image in image .resizable() .aspectRatio(contentMode: .fill) } placeholder: { Rectangle() .fill(Color(UIColor.systemGray5)) } .clipped() .id(content.id)
https://lucasvandongen.dev/swiftui_uitableviewcell_reuse_id.php
こちらを参考に画像の再読み込み時に同じ id の画像を取得するようにしてみましたが、チラつきは改善されませんでした。
チラつかない理由の考察
1. View に .id() を設定
View を識別する .id() を入れることで明示的に View の同一性を指定しました。Explicit identity とも言います。 推測ですが id が同じであればセル更新前と同じ View として再利用され、 WebImage は既存のインスタンスが再利用されることでキャッシュされた画像が即座に表示されていると考えています。結果として画像の再更新がなくなり、チラつきが発生しなくなっているではと考えています。
2. SDWebImage を使用
AsynceImage について公式ドキュメントを確認するとキャッシュ機能について言及されていませんでした。 そして、以下の記事では AsyncImage はキャッシュされないと検証していました。
https://matteomanferdini.com/swiftui-asyncimage/
今回の検証で動作確認した中で、 AsyncImage は画像のキャッシュをしてないように見えました。それが要因の一つで読み込みが毎度発生していたのではと考えています。 一方 SDWebImage はデフォルトの状態でもキャッシュが使用されていることで、 チラつきが抑えられているのではと考えています。
まとめ
当初は UIHostingConfiguration の再生成が原因だと考えていましたが、画像キャッシュの仕組みが主な原因であることがわかりました。 AsyncImage は iOS 標準のライブラリですが、キャッシュ機能を持たないため、画像表示の更新が頻繁に発生する画面では取り扱いに注意すべきと思いました。。静的な画像表示や更新頻度の低い画面では問題ありませんが、今回のように UICollectionView でセルの表示切り替えが頻繁に発生する画面では、SDWebImage のようなキャッシュ機能を持つライブラリの採用を検討しても良さそうです。 SwiftUI の .id() による Explicit Identity(明示的な同一性)の管理が、UIHostingConfiguration において画像キャッシュを機能させる要素になっていると考えています。SwiftUI で実装するときに参考にしていただけると嬉しいです。