コンテンツへスキップ

AsyncImageは.resizable()をサポートしない

SwiftUIのAsyncImageは.resizable()モディファイアをサポートしておらず、フェーズベースの回避策が必要です。

問題

SwiftUIのAsyncImageはリモート画像のロードに便利ですが、意外な制限があります:.resizable()モディファイアを適用できません。以下のコードはコンパイルされますが、期待通りに動作しません:

// This does NOT work as intended
AsyncImage(url: imageURL)
   .resizable()  // Has no effect -- AsyncImage is not an Image
   .aspectRatio(contentMode: .fill)
   .frame(width: 200, height: 200)

理由は、AsyncImageImageではなく、ロード状態を管理するコンテナビューだからです。.resizable()モディファイアはImageにのみ定義されているため、AsyncImageに適用するとジェネリックなViewバージョンが呼ばれ、有用な効果がありません。

解決策

解決策は、ロード完了後に基となるImage値に直接アクセスできるフェーズベースのイニシャライザを使用することです:

AsyncImage(url: imageURL) { phase in
   switch phase {
   case .success(let image):
      image
         .resizable()
         .aspectRatio(contentMode: .fill)
   case .failure:
      Image(systemName: "photo")
         .foregroundStyle(.secondary)
   case .empty:
      ProgressView()
   @unknown default:
      EmptyView()
   }
}
.frame(width: 200, height: 200)
.clipped()

.successケース内では、imageは実際のImage値なので、.resizable()が正しく動作します。これにより、ローディング状態やエラー状態も制御でき、いずれにせよベタープラクティスです。

手動でロードすべき場合

生の画像データが必要な場合(キャッシュ、サイズの確認、UIImageの作成など)は、AsyncImageを完全にスキップしてURLSessionでロードした方がよいかもしれません。しかし、ほとんどの表示専用ケースでは、フェーズベースのイニシャライザが追加の複雑さなしにニーズをカバーします。

これはSwiftUIのAPIの中で、シンプルなイニシャライザが魅力的に見えるものの実際には不十分なケースの一つです。画像固有のモディファイアが必要な場合は、常にフェーズベースのバージョンをデフォルトとして使用してください。

参考になりましたか?BlueskyMastodonでフォローして、Swiftのヒントやインディー開発の最新情報をチェックしてください。