agentsclimarketplace

Swift infinite scroll video feed ios26

Skill esaldgut/ai-native-engineering-workspace/global-skills/apple/swift-infinite-scroll-video-feed-ios26

AI-native engineering workspace — 42 Claude Code agent skills, platform-base workflow docs, and a freshness system that re-verifies each pattern against vendor docs.

Install
npx -y skills add esaldgut/ai-native-engineering-workspace --skill swift-infinite-scroll-video-feed-ios26

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Build a vertical, full-screen video feed (TikTok/Reels-style) in SwiftUI with the memory discipline AVFoundation requires — a bounded AVPlayer LRU pool of ~3 (current ± 1), not 50 players; vertical paging via .scrollTargetBehavior(.paging) + .containerRelativeFrame(.vertical); visibility-driven play/pause with onScrollVisibilityChange; "load more" fired by onScrollTargetVisibilityChange (NOT .onAppear, which fires prematurely in a lazy stack); cursor pagination at the data layer; a skeleton shimmer via phaseAnimator; and optimistic like/follow updates. Documents tearing down AVPlayerLayer for off-screen cells and pinning the pool to @MainActor. Use when implementing an autoplaying scroll feed and you must keep memory and battery bounded.

SKILL.md

10.8 KB, ~2.3k tokens by cl100k_base, as published. Nobody here has run it

Infinite-scroll video feed (SwiftUI + AVFoundation, iOS 26)

There's no Apple "feed" primitive — a vertical video feed is composition plus memory discipline. Apple's own developer-forum guidance is blunt: don't keep dozens of AVPlayers alive. The canonical shape is a bounded LRU player pool (current ± 1, ~3 players) driving cells that page with .scrollTargetBehavior(.paging) and .containerRelativeFrame(.vertical), autoplay gated by onScrollVisibilityChange, and pagination fired by onScrollTargetVisibilityChange. This skill is opinionated on the pool size — 3 is a sane default, but it's a tunable, not an Apple constant.

When to invoke

  • You're building an autoplaying, full-screen, vertically-paged feed and must keep memory and battery bounded.
  • You need cursor-based "load more" that fires reliably (not the broken .onAppear-on-last-cell trick).
  • You're adding skeleton/shimmer placeholders or optimistic like/follow updates to a feed.

Announce on invoke: "Using swift-infinite-scroll-video-feed-ios26 for the AVPlayer LRU pool, paging, visibility autoplay, and cursor pagination."

Do not reach for this for a short, fixed list of videos — a handful of VideoPlayers without a pool is fine. The pool earns its keep at feed scale.

The canonical APIs (verified)

APISignature / form (verified)iOS gateUse
.scrollTargetBehavior(.paging)static var paging: PagingScrollTargetBehavior17.0+Snap one cell per swipe
.containerRelativeFrame(_:alignment:)func containerRelativeFrame(_ axes: Axis.Set, alignment: Alignment = .center) -> some View17.0+Make each cell full-screen
.onScrollVisibilityChange(threshold:_:)func onScrollVisibilityChange(threshold: Double = 0.5, _ action: @escaping (Bool) -> Void) -> some View18.0+Play/pause by visibility
.onScrollTargetVisibilityChange(idType:threshold:_:)fires with the set of visible target ids18.0+"Load more" trigger
AVPlayer / AVQueuePlayer / AVPlayerLooperclass4.1+The player(s); looper for repeat
AVPlayerItem.preferredForwardBufferDurationvar preferredForwardBufferDuration: TimeInterval10.0+Pre-roll only ~1 chunk
AVPlayer.preferredPeakBitRatevar preferredPeakBitRate: Double8.0+Cap quality on cellular
.phaseAnimator(_:content:animation:)func phaseAnimator<Phase>(_ phases: some Sequence, content:, animation:) -> some View17.0+Shimmer placeholder
AVAssetDownloadURLSessionclass9.0+True offline HLS (heavyweight)

The rules (load-bearing)

1. Bounded LRU player pool, pinned to @MainActor

Keep at most ~3 players (visible ± 1). On eviction, tear down, don't just pause. The pool mutates shared state under concurrent play/pause calls coming from visibility events, so isolate it on @MainActor (or make it an actor) — otherwise you violate Swift 6 Sendable.

@MainActor
final class PlayerPool {
    private var players: [URL: AVPlayer] = [:]
    private var order: [URL] = []
    private let capacity = 3
    func player(for url: URL) -> AVPlayer {
        if let p = players[url] { return p }
        let item = AVPlayerItem(url: url)
        item.preferredForwardBufferDuration = 1.0          // pre-roll ~1 HLS chunk
        let p = AVPlayer(playerItem: item)
        players[url] = p; order.append(url)
        if order.count > capacity, let evict = order.first {
            players[evict]?.replaceCurrentItem(with: nil)  // real teardown
            players[evict] = nil; order.removeFirst()
        }
        return p
    }
}

2. Tear down AVPlayerLayer for off-screen cells, not just pause()

Pausing keeps decode/render resources alive. For cells scrolled out of the pool window, remove the player layer entirely (set the item to nil). This is the single biggest memory win in a feed.

3. Each cell is full-screen via .containerRelativeFrame(.vertical); paging snaps it

ScrollView {
    LazyVStack(spacing: 0) {
        ForEach(items) { item in
            FeedCell(item: item).containerRelativeFrame([.horizontal, .vertical])
        }
    }
    .scrollTargetLayout()
}
.scrollTargetBehavior(.paging)

4. Autoplay with onScrollVisibilityChange — tune the threshold up for video

Default threshold is 0.5; for a video feed, 0.6–0.75 avoids flicker as cells cross. Gate player.play() / player.pause() on the boolean it hands you.

5. "Load more" fires from onScrollTargetVisibilityChange, NOT .onAppear

.onAppear on the last cell of a LazyVStack fires prematurely because lazy realization runs ahead of the viewport. Use onScrollTargetVisibilityChange (iOS 18+) and request the next cursor page when a near-the-end id becomes visible. Cursor pagination itself is a data-layer concern (a nextToken/cursor in the response), not a SwiftUI API.

6. Skeleton shimmer with phaseAnimator; optimistic updates flip state immediately

A LinearGradient masked and driven by .phaseAnimator gives a dependency-free shimmer. For like/follow, mutate the @Observable item optimistically, fire the request, and revert on failure — don't block the UI on the network.

Canonical example

struct VideoFeed: View {
    @State private var store: FeedStore                     // @MainActor @Observable, owns the pool
    var body: some View {
        ScrollView {
            LazyVStack(spacing: 0) {
                ForEach(store.items) { item in
                    FeedCell(item: item, player: store.pool.player(for: item.videoURL))
                        .containerRelativeFrame([.horizontal, .vertical])
                        .onScrollVisibilityChange(threshold: 0.7) { visible in
                            store.setPlaying(item.id, visible)        // play/pause
                        }
                }
            }
            .scrollTargetLayout()
        }
        .scrollTargetBehavior(.paging)
        .ignoresSafeArea()
        .onScrollTargetVisibilityChange(idType: FeedItem.ID.self) { visibleIDs in
            if store.isNearEnd(visibleIDs) { Task { await store.loadNextPage() } }  // cursor pagination
        }
    }
}

Decision aid: when NOT to / trade-offs

  • Pool size is a tunable. 3 is conservative; a high-end device can hold 4–5, a low-RAM device may want 2. Don't hard-code it as if it were an Apple constant.
  • CachingPlayerItem is third-party, not Apple. If you need in-feed caching, know that AVFoundation has no "download-while-streaming" primitive — the supported offline path is the heavyweight AVAssetDownloadURLSession, or a local reverse proxy. Mention it; don't pretend it's free.
  • Don't autoplay with sound by default — respect the silent switch and start muted, matching feed conventions and HIG.

Related skills

  • global-skills/apple/swift-ios26-native-ux-patterns/SKILL.md — the zoom transition and visibility-autoplay modifier shared with this feed.
  • global-skills/apple/swift-clean-architecture-module-scaffold/SKILL.md — the @MainActor pool isolation and Sendable cursor-paginated repository.
  • global-skills/meta/skill-pattern-freshness-audit/SKILL.md — re-verify scroll + AVFoundation APIs.

Sources


Last verified: 2026-06-03 against Apple Developer docs (.paging iOS 17, onScrollVisibilityChange / onScrollTargetVisibilityChange iOS 18, preferredForwardBufferDuration confirmed live). The AVPlayer-pool sizing and "tear down, don't pause" guidance trace to Apple developer-forum advice on feed memory. Re-check after: WWDC26, or by 2026-12-01. Decay risk: medium (scroll-visibility APIs are iOS 18-new). Found a drift? Run /skill-pattern-freshness-audit apple.

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Gives 0 of the 12 instructions most video audio skills give in ~2.3k tokens

Counted across 622 of the 795 authors here whose files we hold, read 2026-08-07

  • Read individual rule files for detailed explanationsin 21 of 622, across 10 files
  • Render final videoin 13 of 622, across 6 files
  • Use WAV PCM 16kHz mono audio formatin 12 of 622, across 3 files
  • Use this skill when dealing with Remotion codein 11 of 622, across 4 files
  • Save generated audio to a WAV filein 11 of 622, across 4 files
  • Handle conversion errors gracefullyin 10 of 622, across 6 files
  • Add captions to videos alwaysin 10 of 622, across 4 files
  • Generate music from text descriptions using MusicGenin 9 of 622, across 2 files
  • Do not skip pipeline layersin 9 of 622, across 3 files
  • Do not make one tool do everythingin 9 of 622, across 3 files
  • Use Azure Document Intelligence for complex PDFsin 9 of 622, across 4 files
  • Never ask the user to paste their full API keyin 9 of 622, across 3 files

Said here and by no other author read

  • use a bounded player pool
  • tear down off-screen player items
  • pin the player pool to the main actor
  • make each cell full-screen
  • use paging scroll behavior
  • gate play and pause on scroll visibility

Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.