The NowPlaying framework introduces a first-class Swift API for surfacing app media in system-wide now-playing surfaces โ Lock Screen, Control Center, Dynamic Island, StandBy, CarPlay, Apple Watch, and Apple TV โ via a declarative MediaSessionRepresentable protocol. It also supports remote media sessions (for controlling external speakers/TVs) and Media Sharing Extensions for routing media to third-party devices.
โข Replaces the older MPNowPlayingInfoCenter/MPRemoteCommandCenter approach with a clean, observable, protocol-driven API that integrates directly with SwiftUI and @Observable
โข A single MediaSession object automatically keeps all system now-playing surfaces in sync without manual update calls
โข Remote media sessions and Media Sharing Extensions let apps control playback on smart speakers and third-party devices through the unified system UI, removing the need to embed third-party SDKs
Shows how to conform a player model to MediaSessionRepresentable so that an ambient-sound app appears on the Lock Screen, Control Center, and Dynamic Island with play/pause and skip controls.
import NowPlaying
import AVFoundation
import SwiftUI
// MARK: - Sound model
struct AmbientSound: Identifiable {
let id: String
let name: String
let description: String
let imageName: String
}
// MARK: - Player model that drives the Now Playing UI
@Observable
final class AmbientPlayerModel: MediaSessionRepresentable {
// MediaSessionRepresentable requirement
let id = "com.example.ambientplayer.session"
private(set) var sounds: [AmbientSound] = [
AmbientSound(id: "rain", name: "Rain", description: "Gentle rainfall", imageName: "cloud.rain"),
AmbientSound(id: "forest", name: "Forest", description: "Birds & leaves", imageName: "leaf"),
AmbientSound(id: "waves", name: "Waves", description: "Ocean surf", imageName: "water.waves")
]
private(set) var currentIndex = 0
private(set) var isPlaying = false
// MARK: MediaSessionRepresentable โ content
var content: some MediaContentRepresentable {
let sound = sounds[currentIndex]
return GenericContent(id: sound.id) {
$0.title = sound.name
$0.subtitle = sound.description
$0.mediaType = .audio
$0.duration = .continuous
$0.artwork = MediaArtwork { size in
// Return a system-symbol image scaled to the requested size
UIImage(systemName: sound.imageName)??
UIImage(systemName: "music.note")!
}
}
}
// MARK: MediaSessionRepresentable โ playback state
var playbackSnapshot: PlaybackSnapshot {
PlaybackSnapshot(isPlaying: isPlaying)
}
// MARK: MediaSessionRepresentable โ supported commands
var commands: [MediaCommand] {
[
.play { [weak self] in self?.play() },
.pause { [weak self] in self?.pause() },
.next { [weak self] in self?.skipToNext() }
]
}
// MARK: Playback control
func play() { isPlaying = true }
func pause() { isPlaying = false }
func skipToNext() {
currentIndex = (currentIndex + 1) % sounds.count
}
}
// MARK: - App entry point wiring
@main
struct AmbientApp: App {
@State private var player = AmbientPlayerModel()
// MediaSession must stay alive; store as @State so SwiftUI owns it.
@State private var session: MediaSession?
var body: some Scene {
WindowGroup {
ContentView(player: player)
.onAppear {
// One line to connect your model to the system.
session = MediaSession(representation: player)
}
}
}
}
// MARK: - Minimal content view
struct ContentView: View {
let player: AmbientPlayerModel
var body: some View {
VStack(spacing: 24) {
Image(systemName: player.sounds[player.currentIndex].imageName)
.font(.system(size: 80))
Text(player.sounds[player.currentIndex].name).font(.title)
Text(player.sounds[player.currentIndex].description).foregroundStyle(.secondary)
HStack(spacing: 40) {
Button(player.isPlaying ? "Pause" : "Play") {
player.isPlaying ? player.pause() : player.play()
}
Button("Next") { player.skipToNext() }
}.buttonStyle(.borderedProminent)
}
.padding()
}
}iOS 27 adds sectioned queries, codable model attributes, ResultsObserver for non-SwiftUI change observation, and HistoryObserver for reacting to persistent history changes in SwiftData.
USDKit is a new first-party Swift framework introduced in iOS/macOS 27 that brings native USD scene creation, composition, modification, and export capabilities to Apple platform apps, with deep RealityKit and Spatial Preview integration.
LiveCommunicationKit is the modern replacement for CXProvider that delivers rich, native conversation UIs integrated with the Lock Screen, Dynamic Island, Phone app Recents, and Siri. It provides a unified lifecycle model for audio and video conversations with a single delegate-driven action pipeline.
MediaSession must be kept alive (not deallocated) for the session to remain active โ store it as a property alongside your audio engine. GenericContent suits ambient/non-standard media; use Music, Podcast, or MovieContent for richer metadata. The commands closure is called on an arbitrary queue โ dispatch UI updates to the main actor.
Remote media sessions require APNs infrastructure for push-based state updates; Media Sharing Extensions require compatible third-party device protocols
Xcode 27 introduces an agentic localization workflow that lets you ask a coding agent to translate your entire app directly inside Xcode, leveraging String Catalog context โ including where and how strings are used โ to produce accurate, consistent translations across all languages.