この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
SwiftUIではImage(url:)
のようなAPIは無い為、URL
からImage
を表示する方法を調べました。
環境
- Xcode 13.3
- iOS 15.5
URLからImageを表示する
iOS 15以上
iOS 15以上からはAsyncImage
を使うことで容易に実現出来ます。
AsyncImage
今回はinit(url:scale:content:placeholder:)
を使用してみます。
init<I, P>(
url: URL?,
scale: CGFloat = 1,
content: @escaping (Image) -> I,
placeholder: @escaping () -> P
) where Content == _ConditionalContent<I, P>, I : View, P : View
- url
- 表示する画像のURL
- scale
- 画像に使用するスケール
- content
- 読み込まれた画像を表示するViewを返すクロージャー
- placeholder
- ロードが完了するまでに表示するViewを返すクロージャー
使用例
import SwiftUI
struct ContentView: View {
let imageUrl = URL(string: "https://cdn-ssl-devio-img.classmethod.jp/wp-content/uploads/2022/08/swiftui-image-from-url-eyecatch-960x504.png")
var body: some View {
AsyncImage(url: imageUrl) { image in
image.resizable()
} placeholder: {
ProgressView()
}
.frame(width: 240, height: 126)
}
}
今回はロードが完了するまでに表示するViewはprogressView
にしています。まだ、URL
から画像が取得出来ていない場合は、下記にような表示になります。
ロードが完了すると無事に意図した画像が表示されました。
iOS 15 未満
iOS 15では容易に実現できることが分かったのですが、iOS 15未満での対応方法も試行錯誤してみました。
AsyncImageView
URL
とplaceholder
となるContent
を受け取り、Image
またはplaceholder
を表示するViewです。
import SwiftUI
struct AsyncImageView<Content: View>: View {
@StateObject var viewModel: AsyncImageViewModel
let placeholder: Content
init(url: URL?,
@ViewBuilder placeholder: () -> Content) {
_viewModel = StateObject(wrappedValue: AsyncImageViewModel(url: url))
self.placeholder = placeholder()
}
var body: some View {
ZStack {
if let uiImage = viewModel.uiImage {
Image(uiImage: uiImage)
.resizable()
} else {
placeholder
}
}
.onAppear {
viewModel.downloadImage()
}
}
}
viewModel.uiImage
がnil
の場合はplaceholder
を表示して、nil
でない場合はそのUIImage
からImage
を生成して表示しています。
AsyncViewModel
初期化時にURL
を受け取っています。
URL
からData
を取得する関数downloadImageData
を作成しました。またPublished
のuiImage
の値を更新することでUIが更新される為、@MainActor
をつけたdownloadImage
関数を作成しています。
class AsyncImageViewModel: ObservableObject {
@Published var uiImage: UIImage? = nil
let url: URL?
init(url: URL?) {
self.url = url
}
@MainActor
func downloadImage() {
guard let data = downloadImageData() else { return }
uiImage = UIImage(data: data)
}
private func downloadImageData() -> Data? {
guard let url = url else { return nil }
let data = try? Data(contentsOf: url)
return data
}
}
uiImage
の値を変更することで、AsyncImageView
のUIが変更される
ContentView
import SwiftUI
struct ContentView: View {
let imageUrl = URL(string: "https://cdn-ssl-devio-img.classmethod.jp/wp-content/uploads/2022/08/swiftui-image-from-url-eyecatch-960x504.png")
var body: some View {
AsyncImageView(url: imageUrl) {
ProgressView()
}
.frame(width: 240, height: 126)
}
}
iOS 15未満でもURL
から画像を表示することができました!
おわりに
iOS 15からはかなりサクッとURL
からImage
を表示できることが分かりました。まだローディング最中のUIも決めることが出来るので視覚的にも伝えやすくユーザーに優しいですね。
iOS 15未満でもURL
からImage
を表示することが出来たのですが、より良い方法等ありましたら優しく教えていただけたら嬉しいです。