SwiftUIで、非同期で画像を読み込む際に、詰まったので記事として記しておく。
初学者記事のため、疑いながら読んでください。
SwiftUIで下画像のような画面を、APIを用いて表示する際に、
推奨しないサンプルコード
import SwiftUI
struct PokemonImageView: View {
let imageUrl: URL?
var body: some View {
if let imageUrl = imageUrl,
let imageData = try? Data(contentsOf: imageUrl) {
return Image(uiImage: UIImage(data: imageData)!)
.resizable()
.aspectRatio(contentMode: .fit)
} else {
return Image(systemName: "questionmark.circle")
.resizable()
.aspectRatio(contentMode: .fit)
}
}
}
推奨しないサンプルコード
で、実装した。
紫の警告が表示され、詳しく見てみると、
Synchronous URL loading of https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/1.png should not occur on this application's main thread as it may lead to UI unresponsiveness. Please switch to an asynchronous networking API such as URLSession.
この警告は、メインスレッドで同期的にURLを読み込んでいることによって、ユーザーインターフェースが応答しなくなる可能性があるという意味です。丁寧に該当するコードの部分で、「非同期で行った方が良いよ」と注意してくれている。
対処法
非同期で行うためには、AsyncImage
を用いることが必要です。
実装例
サンプルコード
struct PokemonImageView: View {
let imageUrl: URL?
var body: some View {
if let imageUrl = imageUrl {
AsyncImage(url: imageUrl) { phase in
switch phase {
case .empty:
Image(systemName: "questionmark.circle")
.resizable()
.aspectRatio(contentMode: .fit)
case .success(let image):
image
.resizable()
.aspectRatio(contentMode: .fit)
case .failure:
Image(systemName: "xmark.circle")
.resizable()
.aspectRatio(contentMode: .fit)
@unknown default:
fatalError()
}
}
} else {
Image(systemName: "questionmark.circle")
.resizable()
.aspectRatio(contentMode: .fit)
}
}
}
実装例の詳細
AsyncImage(url: URL(string: "https://example.com/icon.png"))
上記のコードを、body
で実装するだけでも、使用することは可能である。だが、上記のURLで画像取得に失敗した場合に、下画像の赤枠のような表示になり、読み込んでいるのかどうなのかが不明になってしまう。
そのため、エラーに対して表示内容を変更する必要がある。そこで、AsyncImagePhase
を用いる。
AsyncImagePahse
(列挙型)の詳細を見ていく。
今回使用したのは、
empty
まだ画像を読み込んでいない
success
画像の読み込みが成功したとき、取得したImageを使用可能
failure
画像の読み込みに失敗したとき、取得したErrorを使用可能
である。
これらを用いて、条件に合ったUIを表示する。
以上です。
他にも良い方法があれば、コメントいただけると大変うれしいです。
良かったと思ったら、いいねやTwitterのフォローよろしくお願いいたします!
https://sites.google.com/view/muranakar
個人でアプリを作成しているので、良かったら覗いてみてください!
参考