From e63cb8d253aaa57a399930d0eee7a67d9619aec6 Mon Sep 17 00:00:00 2001 From: Nick Reisenauer Date: Wed, 13 May 2026 04:06:30 -0700 Subject: [PATCH] perf: lazy-render episode lists and tighten Kingfisher caching Addresses the SIMALYTICS-IOS-3 N+1 image-burst Sentry issue by cutting the per-screen request count and fixing settings that were defeating Kingfisher's own caches. - ShowDetailView, AnimeDetailView: switch the episodes container from VStack to LazyVStack so off-screen thumbnails don't fetch up front - CustomKFImage: remove .fromMemoryCacheOrRefresh(true), which was skipping the disk cache on every memory miss; remove .serialize(as: .JPEG), which stripped alpha from PNG sources (TMDB streaming logos); add DownsamplingImageProcessor sized to the view frame to reduce decoded-image memory - SimalyticsApp: cap ImageCache disk storage at 500 MB so it LRU-evicts instead of growing unbounded Co-Authored-By: Claude Opus 4.7 (1M context) --- simalytics/Components/CustomKFImage.swift | 3 +-- simalytics/SimalyticsApp.swift | 3 +++ simalytics/Views/Explore/AnimeDetailView.swift | 2 +- simalytics/Views/Explore/ShowDetailView.swift | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/simalytics/Components/CustomKFImage.swift b/simalytics/Components/CustomKFImage.swift index f0a28c7..99e64ff 100644 --- a/simalytics/Components/CustomKFImage.swift +++ b/simalytics/Components/CustomKFImage.swift @@ -23,9 +23,8 @@ struct CustomKFImage: View { ProgressView() } .resizable() - .serialize(as: .JPEG) + .setProcessor(DownsamplingImageProcessor(size: CGSize(width: width, height: height))) .cacheMemoryOnly(memoryCacheOnly) - .fromMemoryCacheOrRefresh(true) .memoryCacheExpiration(.days(7)) .diskCacheExpiration(.days(30)) .cancelOnDisappear(true) diff --git a/simalytics/SimalyticsApp.swift b/simalytics/SimalyticsApp.swift index 05b94cc..19915c6 100644 --- a/simalytics/SimalyticsApp.swift +++ b/simalytics/SimalyticsApp.swift @@ -6,6 +6,7 @@ // // swift-format lint ./Documents/GitHub/simalytics-ios/ -r +import Kingfisher import Sentry import SimpleKeychain import SwiftData @@ -61,6 +62,8 @@ struct SimalyticsApp: App { }() init() { + ImageCache.default.diskStorage.config.sizeLimit = 500 * 1024 * 1024 + SentrySDK.start { options in if let configuredDSN = (Bundle.main.infoDictionary?["SENTRY_DSN"] as? String)? .trimmingCharacters(in: .whitespacesAndNewlines), diff --git a/simalytics/Views/Explore/AnimeDetailView.swift b/simalytics/Views/Explore/AnimeDetailView.swift index 7a34459..5af2e09 100644 --- a/simalytics/Views/Explore/AnimeDetailView.swift +++ b/simalytics/Views/Explore/AnimeDetailView.swift @@ -201,7 +201,7 @@ struct AnimeDetailView: View { if animeDetails?.anime_type == "tv" { if !filteredEpisodes.isEmpty { - VStack(alignment: .leading) { + LazyVStack(alignment: .leading) { HStack { Menu { ForEach(seasons, id: \.self) { season in diff --git a/simalytics/Views/Explore/ShowDetailView.swift b/simalytics/Views/Explore/ShowDetailView.swift index 8f73a03..b1b0e2f 100644 --- a/simalytics/Views/Explore/ShowDetailView.swift +++ b/simalytics/Views/Explore/ShowDetailView.swift @@ -200,7 +200,7 @@ struct ShowDetailView: View { Spacer() if !filteredEpisodes.isEmpty { - VStack(alignment: .leading) { + LazyVStack(alignment: .leading) { HStack { Menu { ForEach(seasons, id: \.self) { season in