Skip to content

perf: lazy-render episode lists and tighten Kingfisher caching#12

Merged
NickReisenauer merged 1 commit into
mainfrom
perf/image-loading-improvements
May 15, 2026
Merged

perf: lazy-render episode lists and tighten Kingfisher caching#12
NickReisenauer merged 1 commit into
mainfrom
perf/image-loading-improvements

Conversation

@NickReisenauer

Copy link
Copy Markdown
Contributor

Summary

  • Addresses Sentry SIMALYTICS-IOS-3 (N+1 image burst on __backButtonAction) by switching the episodes list in ShowDetailView / AnimeDetailView from VStack to LazyVStack — off-screen episode thumbnails no longer fetch up front.
  • Cleans up CustomKFImage so Kingfisher's caches actually work as intended: removes .fromMemoryCacheOrRefresh(true) (was skipping the disk cache on every memory miss) and .serialize(as: .JPEG) (was stripping alpha from PNG sources like TMDB streaming-service logos). Adds a DownsamplingImageProcessor sized to the view frame to lower decoded-image memory.
  • Caps ImageCache.default.diskStorage at 500 MB in SimalyticsApp.init() so the disk cache LRU-evicts instead of growing unbounded.

Test plan

  • Open a TV show with a long season; scroll through episodes — thumbnails should fade in lazily as rows enter view, not all at once.
  • Same check on an anime detail view.
  • Open JustWatchView, force-quit, reopen — streaming-service logos should keep PNG transparency on cache hit (previously re-encoded to JPEG on disk).
  • Repeat navigation into the same detail view feels snappier on subsequent visits (disk cache now actually used for memoryCacheOnly: false callers).
  • No visible regressions on poster lists (TVListView, MovieListView, AnimeListView) or detail screen posters.

🤖 Generated with Claude Code

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) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Targets a Sentry-reported image-loading burst on episode lists and tightens Kingfisher cache configuration so disk caching, PNG transparency, and decoded-image memory all behave correctly.

Changes:

  • Switch the episode list VStack to LazyVStack in ShowDetailView and AnimeDetailView so off-screen episode thumbnails are no longer fetched up front.
  • Replace .serialize(as: .JPEG) and .fromMemoryCacheOrRefresh(true) in CustomKFImage with a DownsamplingImageProcessor sized to the view frame.
  • Cap ImageCache.default.diskStorage at 500 MB during SimalyticsApp.init().

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
simalytics/Views/Explore/ShowDetailView.swift Lazy-renders the episodes list to avoid fetching all thumbnails on appear.
simalytics/Views/Explore/AnimeDetailView.swift Same lazy-render fix for the anime episode list.
simalytics/SimalyticsApp.swift Imports Kingfisher and sets a 500 MB disk-cache size limit at startup.
simalytics/Components/CustomKFImage.swift Drops JPEG re-serialization and forced refresh, adds size-based downsampling processor.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

}
.resizable()
.serialize(as: .JPEG)
.setProcessor(DownsamplingImageProcessor(size: CGSize(width: width, height: height)))
}
.resizable()
.serialize(as: .JPEG)
.setProcessor(DownsamplingImageProcessor(size: CGSize(width: width, height: height)))
}()

init() {
ImageCache.default.diskStorage.config.sizeLimit = 500 * 1024 * 1024
@NickReisenauer NickReisenauer merged commit e63cb8d into main May 15, 2026
1 check passed
NickReisenauer added a commit that referenced this pull request May 15, 2026
Three follow-ups from PR #12:

1. CustomKFImage now caches the original (un-downsampled) image to disk
   so the same poster URL shared by lists (80x118, 100x147) and details
   (150x220.59, 150x225) hits a single disk entry. The processor still
   runs to bound memory, but cache-key fragmentation across view sizes
   is gone.
2. Clamp the processor size to at least 1pt in each dimension. Guards
   against any caller that passes a zero-sized frame, which would
   otherwise produce a zero bitmap, silently fall back to the original,
   and pollute the cache key with the zero size.
3. Pull the 500 MB disk-cache size limit out to a named constant with
   an explaining comment.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@NickReisenauer

Copy link
Copy Markdown
Contributor Author

Addressed all three Copilot points in 9786734 on main. The original-image disk caching is the main fix — same poster URL across list and detail views now shares one entry, restoring the snappy-on-return goal.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants