Как сохранить ссылку на данные при использовании ObservableObject
Я новичок в Swiftui, и мне трудно понять, как правильно сохранить данные, созданные в ObservableObject, при рендеринге представлений? А может быть, совсем другой подход к проблеме?
В частности, речь идет о получении данных HTTP в каждой строке в List().
Прямо сейчас он слишком часто выполняет HTTP-вызов при рендеринге родительских представлений, что приводит к перезагрузке всех строк.
Ту же проблему можно найти здесь: Сохраните ссылку на модель представления / данных после обновления представления
public class VideoFetcher: ObservableObject {
@Published var video: VideoResponse?
@Published var coverImage: UIImage?
@Published var coverImageLoading = false
@Published var categories: String?
@Published var loading = false
@Published var error = false
func load(mediaItemSlug: String = "", broadcasterSlug: String = "") {
self.loading = true
Video.findBySlug(
mediaItemSlug: mediaItemSlug,
broadcasterSlug: broadcasterSlug,
successCallback: {video -> Void in
self.video = video
self.loading = false
self.setCategories()
self.loadCoverImage()
},
errorCallback: {(error, _) -> Void in
self.loading = false
self.error = true
})
}
func loadCoverImage() {
guard self.video!.coverImageUrl != "" else {
return
}
self.coverImageLoading = true
let downloader = ImageDownloader()
let urlRequest = URLRequest(url: URL(string: self.video!.coverImageUrl)!)
let filter = AspectScaledToFillSizeFilter(size: CGSize(width: 520.0, height: 292.499999963))
downloader.download(urlRequest, filter: filter) { response in
if case .success(let image) = response.result {
self.coverImage = image
self.coverImageLoading = false
}
}
}
func setCategories() {
if (self.video!.broadcaster.categories.count > 0) {
let categoryNames = self.video!.broadcaster.categories.map { category in
return category.name == "" ? "(no name)" : category.name
}
self.categories = categoryNames.joined(separator: " • ");
}
}
}
Строка списка ():
struct VideoCard: View {
@ObservedObject var fetcher = VideoFetcher()
...
init() {
// Causes reload each render
self.fetcher.load()
}
var body: some View {
...
.onAppear {
// Loads that on appear but fetcher.video is nil after view re-rendered because load() wasn't called
self.fetcher.load()
}
}
}
1 ответ
Решение
Спасибо, Крис. Я думал, что делаю что-то не так на архитектурном уровне, но добавил кеширование, и это решило мою проблему.
import Alamofire
import AlamofireImage
import Cache
public class VideoFetcher: ObservableObject {
@Published var video: VideoResponse?
@Published var coverImage: UIImage?
@Published var coverImageLoading = false
@Published var broadcasterImage: UIImage?
@Published var categories: String?
@Published var loading = false
@Published var error = false
func load(mediaItemSlug: String = "", broadcasterSlug: String = "") {
let videoCache = try? AppCache.video!.object(forKey: mediaItemSlug)
if (videoCache != nil) {
self.video = videoCache
self.setCategories()
self.loadCoverImage()
return
}
self.loading = true
Video.findBySlug(
mediaItemSlug: mediaItemSlug,
broadcasterSlug: broadcasterSlug,
successCallback: {video -> Void in
try? AppCache.video!.setObject(video, forKey: mediaItemSlug)
self.video = video
self.loading = false
self.setCategories()
self.loadCoverImage()
self.loadBroadcasterImage()
},
errorCallback: {(error, _) -> Void in
self.loading = false
self.error = true
})
}
func loadCoverImage() {
let coverImageUrl = self.video!.coverImageUrl
guard coverImageUrl != "" else {
return
}
let urlRequest = URLRequest(url: URL(string: coverImageUrl)!)
let cachedImage = AppCache.image!.image(for: urlRequest, withIdentifier: coverImageUrl)
if (cachedImage != nil) {
self.coverImage = cachedImage
return
}
self.coverImageLoading = true
let downloader = ImageDownloader(imageCache: AppCache.image!)
let filter = AspectScaledToFillSizeFilter(size: CGSize(width: 520.0, height: 292.499999963))
downloader.download(urlRequest, filter: filter) { response in
if case .success(let image) = response.result {
AppCache.image!.add(image, for: urlRequest, withIdentifier: coverImageUrl)
self.coverImage = image
self.coverImageLoading = false
}
}
}
func loadBroadcasterImage() {
let broadcasterImage = self.video!.broadcaster.avatarImageUrl
guard broadcasterImage != "" else {
return
}
let urlRequest = URLRequest(url: URL(string: broadcasterImage)!)
let cachedImage = AppCache.image!.image(for: urlRequest, withIdentifier: broadcasterImage)
if (cachedImage != nil) {
self.broadcasterImage = cachedImage
return
}
let downloader = ImageDownloader(imageCache: AppCache.image!)
let filter = AspectScaledToFillSizeFilter(size: CGSize(width: 16, height: 16))
downloader.download(urlRequest, filter: filter) { response in
if case .success(var image) = response.result {
image = image.af.imageRoundedIntoCircle()
AppCache.image!.add(image, for: urlRequest, withIdentifier: broadcasterImage)
self.broadcasterImage = image
}
}
}
func setCategories() {
let categories = self.video!.broadcaster.categories
if (categories.count > 0) {
let categoryNames = categories.map { category in
return category.name == "" ? "(no name)" : category.name
}
self.categories = categoryNames.joined(separator: " • ");
}
}
}