Как сохранить ссылку на данные при использовании ObservableObject - PullRequest
0 голосов
/ 05 мая 2020

Я новичок в 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 Ответ

0 голосов
/ 13 мая 2020

Спасибо, Крис. Я думал, что делаю что-то не так на архитектурном уровне, но добавил кеширование, и это решило мою проблему.

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: " • ");
        }
    }
}
...