Integrating your music app with Apple Intelligence
At a glance
| Item |
Summary |
| Purpose |
Integrate audio, playlist, alarm, timer, sharing, Spotlight, and Now Playing experiences with App Intents. |
| Architecture |
A manager-oriented SwiftUI/SwiftData app plus a widget extension for a Home Screen widget and Live Activities. |
| State strategy |
The app entry owns the process graph; specialized managers own data, playback, timers, alarms, rendering, and system sessions. |
| Boundary |
Main app, widget extension, and UI-test targets; shared ActivityKit payload types cross the app/widget target boundary. |
Project structure
CosmoTunes/
├── AppIntents/
│ ├── Audio/ # Entities, queries, playback/share intents
│ ├── Clock/ # Alarm and timer contracts
│ └── TestSupport/
├── Managers/ # Data, playback, indexing, alarms, rendering
├── Model/ # SwiftData and ActivityKit values
├── Views/ # Chat, library, player, alarms, settings
└── CosmoTunesApp.swift # Composition root
CosmoTunesWidget/ # Widget and Live Activity target
CosmoTunesUITests/ # Entity, intent, Spotlight, UI tests
The sample is code-rich, but its top-level organization is consistent: domain contracts under AppIntents, durable values under Model, long-lived capabilities under Managers, and target-specific presentation under app or widget folders.
Overall architecture
flowchart LR
Root["CosmoTunesApp"] --> Views["SwiftUI views"]
Root --> Data["ModelManager / SwiftData"]
Root --> Playback["PlaybackController"]
Playback --> Engine["AudioEngine / AVFoundation"]
Root --> Session["CosmoTunesMediaSession / NowPlaying"]
Playback <--> Session
Root --> Time["Timer + Alarm managers"]
Root --> DI["AppDependencyManager"]
DI --> Intents["Audio and Clock intents"]
Intents --> Data
Intents --> Playback
Widget["CosmoTunesWidgetExtension"] --> Live["WidgetKit / ActivityKit"]
Reference code
CosmoTunes/CosmoTunesApp.swift:92 — the composition root exposes selected managers to all intent and query invocations.
AppDependencyManager.shared.add(dependency: navigation)
AppDependencyManager.shared.add(dependency: playback)
AppDependencyManager.shared.add(dependency: timerController)
AppDependencyManager.shared.add(dependency: model)
AppDependencyManager.shared.add(dependency: donations)
AppDependencyManager.shared.add(dependency: alarmScheduler)
AppDependencyManager.shared.add(dependency: userPreferences)
The root performs wiring, while each manager keeps one operational concern. The widget is a separate executable boundary and consumes shared ActivityKit values rather than the app’s live manager graph.
Ownership and state
classDiagram
CosmoTunesApp *-- ModelManager : strong owner
CosmoTunesApp *-- PlaybackController : strong owner
CosmoTunesApp *-- CosmoTunesMediaSession : strong owner
CosmoTunesApp *-- TimerController : strong owner
PlaybackController *-- AudioEngine : creates
PlaybackController o-- CosmoTunesMediaSession : weak
CosmoTunesMediaSession o-- PlaybackController : weak
ModelManager *-- SpotlightIndexer : owns
ModelManager o-- TrackAudioRenderer : weak
PlayAudioIntent --> ModelManager : injected
PlayAudioIntent --> PlaybackController : injected
Ownership evidence
CosmoTunes/CosmoTunesApp.swift:56 — the root wires non-owning cross-links after creating every long-lived manager.
playback.donations = donations
playback.audioEngine.mediaSession = mediaSession
playback.mediaSession = mediaSession
mediaSession.playback = playback
mediaSession.artworkRenderer = artworkRenderer
| Owner |
Object or state |
Relationship |
Mutation authority |
CosmoTunesApp |
Manager graph and MediaSession wrapper |
Creates and strongly retains |
Composition and lifecycle only |
ModelManager |
ModelContext, generation service, SpotlightIndexer |
Strong ownership; weak optional collaborators |
Persistent library/chat/alarm state and indexing changes |
PlaybackController |
AudioEngine, queue, current track/playlist |
Strong subsystem owner |
Queue and transport state |
AudioEngine |
AVAudio engine, sampler, scheduling task |
Private implementation ownership |
Low-level playback timing and engine state |
CosmoTunesMediaSession |
Tracked Now Playing snapshot |
Strongly retained by app, weak link back to playback |
System-facing media state and commands |
| Widget extension |
Widget and Live Activity configurations |
Separate process/target lifecycle |
Presentation of timeline/activity values |
Class and protocol design
| Type |
Design |
Responsibility |
ModelManager |
@MainActor @Observable final class |
Coordinate SwiftData and hand indexing work to an actor. |
PlaybackController |
@MainActor @Observable final class |
Own the queue and translate user/system commands into engine actions. |
AudioEngine |
@MainActor @Observable final class |
Encapsulate AVFoundation setup, note scheduling, and playback timing. |
CosmoTunesMediaSession |
MediaSessionRepresentable |
Adapt playback state and app entity identifiers to Now Playing. |
PlayAudioIntent |
AudioPlaybackIntent |
Resolve a schema AudioEntity and delegate queue mutations. |
SongEntity / SongQuery |
IndexedEntity, Transferable, query protocols |
Bridge stored tracks to Spotlight, Siri lookup, and file transfer. |
SpotlightWrapping |
app-defined Sendable protocol |
Make Core Spotlight batching replaceable in tests. |
This sample combines framework protocol conformances with one purposeful app-defined seam: SpotlightWrapping is injected into SpotlightIndexer, while concrete playback managers remain directly composed.
Access control
| Symbol |
Access |
Verified effect |
Likely rationale |
| Playback queue snapshots |
private(set) |
Views and system adapters can read current notes/queue/index/playlist; only PlaybackController writes them. |
Preserve queue invariants. |
| AVAudio engine internals |
private |
Engine, sampler, scheduler, and timing state cannot be bypassed by callers. |
Keep AVFoundation lifecycle coherent. |
SongEntity backing MIDI/tempo |
private |
Only the entity’s transfer implementation reads export snapshots. |
Hide non-schema transport data. |
| Cross-manager collaborators |
weak |
Managers may call peers without extending their lifecycle. |
Avoid cycles in the bidirectional playback/session graph. |
SleepTimerAttributes payload members |
public inside an internal outer type |
ActivityKit-facing fields and initializers are explicit; the enclosing type is still not a public library API. |
Make the shared serialization surface clear. |
| Preview/query helpers |
fileprivate |
Widget preview fixtures and alarm enrichment are shared only within their files. |
Keep helper APIs out of target-wide use. |
| Managers, entities, intents |
internal default; no open |
App and extension targets compile their own selected sources without exposing subclassable APIs. |
Executable targets do not need a public framework surface. |
Reference code
CosmoTunes/Managers/PlaybackController.swift:30 — readable playback snapshots have a single mutation owner.
private(set) var currentTrackNotes: [MIDINoteData] = []
private(set) var playQueue: [GeneratedTrack] = []
private(set) var queueIndex = 0
private(set) var currentPlaylist: Playlist?
weak var donations: DonationManager?
weak var mediaSession: CosmoTunesMediaSession?
Logic ownership and placement
| Logic |
Owner |
Placement rationale |
| Object creation and cross-links |
CosmoTunesApp |
One visible composition root controls lifetimes and dependency registration. |
| SwiftData, generation routing, Spotlight change creation |
ModelManager |
Persistent mutations share one main-actor context. |
| Spotlight I/O |
SpotlightIndexer plus SpotlightWrapping |
Actor isolation avoids blocking UI and the protocol supports recording tests. |
| Queue and transport |
PlaybackController |
Higher-level playback invariants remain above AVFoundation details. |
| Sampling and timing |
AudioEngine |
Low-level audio resources stay encapsulated. |
| Lock Screen/control commands |
CosmoTunesMediaSession |
One adapter synchronizes app state with the Now Playing framework. |
| Parameter resolution |
App Intent/entity query files |
System values are converted before invoking domain managers. |
| Widget and Live Activity views |
CosmoTunesWidget target |
Extension presentation is isolated from the app’s runtime graph. |
Design patterns
| Pattern |
Source evidence |
Purpose or tradeoff |
| Composition root / DI |
CosmoTunes/CosmoTunesApp.swift:34 |
Makes a large object graph explicit and shares selected services with intents. |
| Subsystem controller |
CosmoTunes/Managers/PlaybackController.swift:15 |
Separates queue policy from audio implementation. |
| Protocol adapter |
CosmoTunes/Managers/CosmoTunesMediaSession.swift:165 |
Presents app playback through a framework-defined contract. |
| Weak back-reference |
CosmoTunes/Managers/CosmoTunesMediaSession.swift:48 |
Enables remote commands without a retain cycle. |
| Replaceable gateway |
CosmoTunes/Managers/CoreSpotlightWrapper.swift:136 |
Allows deterministic indexing tests behind SpotlightWrapping. |
| Multi-target shared value |
CosmoTunes/Model/SleepTimerAttributes.swift:16 |
Shares ActivityKit payload shape without sharing live managers. |
Naming conventions
- Long-lived capabilities use role suffixes:
Manager, Controller, Engine, Renderer, Indexer, and Session.
- Framework projections use
Entity; framework actions use verb-noun Intent names.
- Methods describe commands at the correct layer:
playTracks, loadAndPlay, publishSnapshot, and performChange.
- Read-only state names describe snapshots (
currentTrackNotes, playQueue, playbackSnapshot).
- Files match primary types, while extensions group protocol conformances such as
EntityQuery, Transferable, and MediaSessionRepresentable.
Architecture takeaways
- Keep a large sample understandable by assigning state to narrowly named subsystem owners and doing all cross-wiring at the app root.
- Put queue policy above the AVFoundation engine and expose read-only snapshots with
private(set).
- Use weak links where two coordinators need to call each other, while one root strongly owns both.
- Share immutable ActivityKit payload types with the widget; do not attempt to share the app’s live dependency graph across targets.
- Add a custom protocol only where replacement materially improves testing, as with Spotlight I/O.
Source map
| Source |
Relevant symbols |
CosmoTunes/CosmoTunesApp.swift:14 |
App composition root and dependency registration |
CosmoTunes/Managers/ModelManager.swift:26 |
Data owner and indexing coordination |
CosmoTunes/Managers/PlaybackController.swift:15 |
Queue and transport owner |
CosmoTunes/Managers/AudioEngine.swift:20 |
AVFoundation implementation boundary |
CosmoTunes/Managers/CosmoTunesMediaSession.swift:26 |
Now Playing adapter |
CosmoTunes/AppIntents/Audio/Intents/PlayAudioIntent.swift:15 |
Audio schema orchestration |
CosmoTunes/AppIntents/Audio/Entities/SongEntity.swift:17 |
Indexed and transferable song adapter |
CosmoTunesWidget/CosmoTunesWidgetBundle.swift:11 |
Widget extension entry |