DispatchQueue загрузка изображений
Не могу разобраться с многопоточностью.
Изображения получаю из сети с помощью URLSession, естественно интерфейс загружается раньше чем подгружаются картинки. Понимаю что нужно использовать DispatchQueue.main.async { }, но не могу сообразить какую часть кода заключить в скобки (если стороку imgData = data, то это не дает результата). Нашел способ через sleep(1), но это настоящий костыль который тормозит приложение.
func getImage(url: String) -> Image {
var imgData = Data()
if let parsedURL = URL(string: url) {
URLSession.shared.dataTask(with: parsedURL) { data, response, error in
if let data = data {
imgData = data
}
}.resume()
}
sleep(1)
if let image = UIImage(data: imgData) {
return Image(uiImage: image)
} else {
return Image(systemName: "photo")
}
}
Ответы (1 шт):
Автор решения: schmidt9
→ Ссылка
Можно добавить параметр для возврата замыкания, поскольку результат возвращается асинхронно
func getImage(url: String, completion: @escaping (Image) -> Void) {
var imgData = Data()
if let parsedURL = URL(string: url) {
URLSession.shared.dataTask(with: parsedURL) { data, response, error in
if let data = data {
imgData = data
if let image = UIImage(data: imgData) {
completion(Image(uiImage: image))
} else {
completion(Image(systemName: "photo"))
}
}
}.resume()
}
}
Дополнение
Для SwiftUI вышеуказанный код не подойдет, все будет несколько сложнее, вот пример, взятый отсюда
import Foundation
import SwiftUI
import Combine
class ImageLoader: ObservableObject {
@Published var image: UIImage?
private let url: URL
private var cancellable: AnyCancellable?
init(url: URL) {
self.url = url
}
deinit {
cancellable?.cancel()
}
func load() {
cancellable = URLSession.shared.dataTaskPublisher(for: url)
.map { UIImage(data: $0.data) }
.replaceError(with: nil)
.receive(on: DispatchQueue.main)
.assign(to: \.image, on: self)
}
func cancel() {
cancellable?.cancel()
}
}
struct AsyncImage<Placeholder: View>: View {
@ObservedObject private var loader: ImageLoader
private let placeholder: Placeholder?
init(url: URL, placeholder: Placeholder? = nil) {
loader = ImageLoader(url: url)
self.placeholder = placeholder
}
var body: some View {
image
.onAppear(perform: loader.load)
.onDisappear(perform: loader.cancel)
}
private var image: some View {
Group {
if loader.image != nil {
Image(uiImage: loader.image!)
.resizable()
} else {
placeholder
}
}
}
}
struct ContentView: View {
var urlString: String
var body: some View {
if urlString != "" {
AsyncImage(
url: URL(string: urlString)!,
placeholder: Text("Loading ...")
).aspectRatio(contentMode: .fit)
}
}
}