Sample CodeiOS, iPadOS, Mac CatalystReviewed 2026-07-21View on Apple Developer

Playing video content in a standard user interface

At a glance

Item Summary
Purpose Plays catalog videos full screen, embedded inline, or in Picture in Picture with AVPlayerViewController.
App architecture A screen-owned PlaybackController is the catalog-level façade; it retains one coordinator per video, and each coordinator manages one player-view-controller lifecycle.
Main patterns Coordinator, keyed registry, weak delegate, OptionSet state model, and lazy framework-resource creation.
Project style One UIKit target organized mostly by primary type, with storyboards for navigation and layout.

Project structure

Using AVKit in iOS/
├── ViewController.swift
├── PlaybackController.swift
├── PlayerViewControllerCoordinator.swift
├── VideoBrowserViewController.swift
├── VideoBrowserCell.swift
├── Video.swift
├── DebugHUD.swift
├── VideoBrowserFlowLayout.swift
└── Base.lproj/Main.storyboard

Structure observations

  • Global playback coordination is separate from collection-view presentation.
  • The coordinator and its AVKit delegate conformance share one file because they participate in the same lifecycle.
  • Small UI collaborators such as the cell, layout, and debug HUD remain separate primary-type files.

Overall architecture

Reference code

Using AVKit in iOS/PlaybackController.swift:30 — the façade reuses the coordinator for a video or creates and registers it once.

func coordinator(for indexPath: IndexPath) -> PlayerViewControllerCoordinator {
    let video = videos[indexPath.row]
    if let playbackItem = playbackItems[video] {
        return playbackItem
    } else {
        let playbackItem = PlayerViewControllerCoordinator(video: video)
        playbackItem.delegate = videoItemDelegate
        playbackItems[video] = playbackItem
        return playbackItem
    }
}

Interpretation

The browser sends presentation requests through PlaybackController rather than constructing players. The registry preserves playback context for each Video, while the coordinator isolates AVKit and UIKit containment rules.

Ownership and state

Ownership evidence

Using AVKit in iOS/PlayerViewControllerCoordinator.swift:27 — the coordinator owns observable playback state but avoids retaining its delegate and full-screen presenter.

weak var delegate: PlayerViewControllerCoordinatorDelegate?
var video: Video
private(set) var status: Status = []
private(set) var playerViewControllerIfLoaded: AVPlayerViewController?
private weak var fullScreenViewController: UIViewController?
Owner Object or state Relationship Mutation authority
ViewController PlaybackController Creates and retains one controller Root screen configures its delegate and lifecycle
PlaybackController Coordinator registry Creates and strongly caches by Video PlaybackController only
Coordinator AVPlayerViewController, player, KVO token, HUDs, Status Creates, observes, and tears down Coordinator methods and AVKit callbacks
Browser cell Coordinator and external HUD references Borrows while visible Cell clears references during reuse

Class and protocol design

Type or protocol Responsibility Depends on
PlaybackController Prepare audio, index content, reuse coordinators, and route presentation operations Video, coordinator
PlayerViewControllerCoordinator Move one playback experience among inline, full-screen, and PiP contexts AVKit, UIKit containment
PlayerViewControllerCoordinatorDelegate Ask an owner with navigation context to restore or realign UI Root view controller
VideoBrowserViewController Render the catalog and translate cell actions into playback requests Playback façade, collection view
VideoBrowserCellDelegate Report a cell tap without making the cell own navigation Browser controller
Status Represent simultaneous playback and transition conditions Swift OptionSet

PlayerViewControllerCoordinatorDelegate is app-defined because the coordinator cannot restore global navigation by itself. AVKit’s own delegate remains implemented directly by the coordinator.

Access control

Symbol Access Verified effect Likely rationale
PlaybackController.playbackItems private Other types cannot replace or mutate the coordinator registry. Preserve one-coordinator-per-video identity.
Coordinator status, externalDebugHud, and playerViewControllerIfLoaded private(set) Collaborators may inspect them, but only the coordinator may replace or mutate them. Support UI restoration and diagnostics without sharing lifecycle authority.
fullScreenViewController private weak Only the coordinator uses it, and it does not retain the presentation hierarchy. Avoid a view-controller ownership cycle.
Loading and HUD helpers private Callers use semantic presentation methods rather than setup primitives. Keep lazy construction and teardown invariant.
AVPlayerViewController helper extension private hasContent(fromVideo:) is visible only in this file. Prevent a sample-specific convenience from becoming module-wide API.
Main app types implicit internal Symbols remain inside the app module. The sample has no library API surface.

Reference code

Using AVKit in iOS/PlayerViewControllerCoordinator.swift:243 — private helpers keep resource creation behind coordinator operations.

extension PlayerViewControllerCoordinator {
    private func loadPlayerViewControllerIfNeeded() {
        if playerViewControllerIfLoaded == nil {
            playerViewControllerIfLoaded = AVPlayerViewController()
        }
    }
}

Logic ownership and placement

Logic Owning type or file Placement rationale
Audio-session preparation and coordinator lookup PlaybackController Shared catalog-level setup and indexing.
Player creation, containment, presentation, and teardown Coordinator One authority manages the AVKit controller lifecycle.
PiP and full-screen status updates Coordinator’s AVKit delegate extension State changes originate at the framework callback boundary.
Navigation-aware PiP restoration ViewController The root has the global navigation context the coordinator intentionally lacks.
Collection cell reuse cleanup VideoBrowserCell Reuse is a view-lifecycle concern.

Design patterns

Pattern Source evidence Purpose or tradeoff
Coordinator Using AVKit in iOS/PlayerViewControllerCoordinator.swift:110 Centralizes movement between full-screen, inline, and PiP UI.
Registry / identity map Using AVKit in iOS/PlaybackController.swift:15 Reuses playback state for the same Video; retained entries grow with the fixed catalog.
Delegate Using AVKit in iOS/PlayerViewControllerCoordinator.swift:310 Returns navigation-dependent work to a more informed owner without retaining it.
OptionSet state model Using AVKit in iOS/PlayerViewControllerCoordinator.swift:269 Represents overlapping conditions such as ready, full-screen, and transitioning.
Lazy resource lifecycle Using AVKit in iOS/PlayerViewControllerCoordinator.swift:245 Creates AVKit UI only when needed and releases it when no longer shown.

Main application flow

Reference code

Using AVKit in iOS/PlaybackController.swift:49 — public façade methods translate index-path requests into coordinator commands.

func embed(contentForIndexPath indexPath: IndexPath,
           in parentViewController: UIViewController,
           containerView: UIView) {
    coordinator(for: indexPath).embedInline(in: parentViewController,
                                            container: containerView)
}

func present(contentForIndexPath indexPath: IndexPath,
             from presentingViewController: UIViewController) {
    coordinator(for: indexPath).presentFullScreen(from: presentingViewController)
}

Naming conventions

  • Role suffixes are explicit: Controller, Coordinator, Delegate, Cell, and FlowLayout.
  • Commands describe UI transitions: presentFullScreen, embedInline, restoreFullScreen, and removeFromParentIfNeeded.
  • Optional-resource names expose lifecycle: playerViewControllerIfLoaded and loadPlayerViewControllerIfNeeded.
  • Boolean-like state uses adjective phrases such as isBeingShown; Status cases name concrete conditions.
  • Files generally match their primary type; framework conformances and small private helpers stay beside the coordinated type.

Architecture takeaways

  • Give one coordinator sole authority over a framework controller that moves between multiple view hierarchies.
  • Cache by domain identity when playback state must survive cell reuse and presentation changes.
  • Expose lifecycle state as read-only with private(set) while keeping mutation local.
  • Use a weak app delegate only for decisions requiring navigation context outside the coordinator.
  • Model overlapping playback conditions with an OptionSet instead of forcing them into one exclusive enum state.

Source map

Source file Relevant symbols
Using AVKit in iOS/ViewController.swift Root playback ownership and PiP restoration delegate
Using AVKit in iOS/PlaybackController.swift Catalog façade and coordinator registry
Using AVKit in iOS/PlayerViewControllerCoordinator.swift AVKit lifecycle, state, and delegate protocols
Using AVKit in iOS/VideoBrowserViewController.swift Collection presentation and user-intent routing
Using AVKit in iOS/VideoBrowserCell.swift Cell delegation and reuse cleanup
Using AVKit in iOS/Video.swift Video identity and catalog model