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
flowchart LR
Root["ViewController"] --> Playback["PlaybackController"]
Root --> Browser["VideoBrowserViewController"]
Browser --> Playback
Playback --> Registry["Video to coordinator registry"]
Registry --> Coordinator["PlayerViewControllerCoordinator"]
Coordinator --> PVC["AVPlayerViewController"]
PVC --> Player["AVPlayer"]
Coordinator -. weak delegate .-> Root
Browser --> Cell["VideoBrowserCell"]
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
classDiagram
ViewController *-- PlaybackController : owns
PlaybackController *-- PlayerViewControllerCoordinator : caches per Video
PlayerViewControllerCoordinator *-- AVPlayerViewController : owns while visible
AVPlayerViewController *-- AVPlayer : retains
PlayerViewControllerCoordinator *-- Status : mutates
PlayerViewControllerCoordinator o-- UIViewController : weak fullscreen reference
PlaybackController o-- CoordinatorDelegate : weak delegate
VideoBrowserCell --> PlayerViewControllerCoordinator : references while displayed
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
sequenceDiagram
actor User
participant Browser as Video browser
participant Playback as PlaybackController
participant Coord as Coordinator
participant AVKit as AVPlayerViewController
User->>Browser: Select video
Browser->>Playback: present or embed index path
Playback->>Playback: find or create coordinator
Playback->>Coord: presentFullScreen or embedInline
Coord->>AVKit: lazily create and attach player
AVKit-->>Coord: delegate lifecycle events
Coord-->>Browser: status-visible playback UI
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 |