ActivityKit in iOS 26: Live Activities on CarPlay, Mac, and iPad
Live Activities shipped with iPhone 14 Pro and the Dynamic Island, and for two years they stayed there — locked to a single device form factor. With iOS 26, Apple blew the doors open. Your Live Activity now renders on CarPlay dashboards, macOS Tahoe menu bars, iPadOS 26 Lock Screens, and even Apple Watch. If you already have a working Live Activity, you are closer to multiplatform than you think — but there are real layout and lifecycle differences you need to handle.
This post covers the iOS 26 ActivityKit expansion: new platform targets, the ActivityContent layout system for
multiple form factors, scheduled and time-bound activities, and what you need to change in existing code. We will not
cover WidgetKit’s separate iOS 26 changes (push reloads, Liquid Glass) — those are in
WidgetKit in iOS 26.
Contents
- The Problem
- ActivityKit’s iOS 26 Platform Expansion
- Adapting Layouts per Form Factor
- Scheduled and Time-Bound Activities
- Advanced Usage
- Performance Considerations
- When to Use (and When Not To)
- Summary
The Problem
Before iOS 26, a Live Activity was an iPhone-only surface. If your app tracked, say, a Pixar movie rendering pipeline, you could show render progress on the Lock Screen and Dynamic Island — but the moment a user glanced at their Mac, CarPlay display, or iPad, that context vanished. You ended up building separate solutions for each platform: a menu bar extra on macOS, a CarPlay template, a standalone iPad widget.
Consider a delivery-tracking activity for Toy Story merchandise:
struct DeliveryAttributes: ActivityAttributes {
let orderName: String
let destinationCity: String
struct ContentState: Codable, Hashable {
var currentStatus: String
var estimatedArrival: Date
var driverName: String
}
}
// Starting the activity — iPhone only before iOS 26
let attributes = DeliveryAttributes(
orderName: "Buzz Lightyear Action Figure",
destinationCity: "Tri-County Area"
)
let initialState = DeliveryAttributes.ContentState(
currentStatus: "Out for delivery",
estimatedArrival: .now.addingTimeInterval(3600),
driverName: "Al McWhiggin"
)
let activity = try Activity.request(
attributes: attributes,
content: .init(state: initialState, staleDate: nil)
)
This code works on iPhone. But on iOS 26, the same activity can appear on CarPlay, Mac, iPad, and Watch — and a single SwiftUI layout cannot serve all those surfaces well. A Dynamic Island compact view crammed into a CarPlay dashboard looks broken. A Lock Screen banner stretched across a 27-inch Studio Display looks absurd.
ActivityKit’s iOS 26 Platform Expansion
iOS 26 extends ActivityKit to four new surfaces beyond
iPhone:
| Platform | Surface | Layout Context |
|---|---|---|
| CarPlay | Dashboard tile, Now Playing | Compact, glanceable, driver-safe |
| macOS Tahoe | Menu bar item, notification banner | Narrow width, always-on-screen |
| iPadOS 26 | Lock Screen banner, LA island | Wider than iPhone, split-view aware |
| watchOS 12 | Smart Stack card | Ultra-compact, wrist-glance optimized |
The key architectural change is that Activity is no longer implicitly iPhone-only. When you call
Activity.request(attributes:content:) on iOS 26, the system broadcasts the activity to every connected device signed
into the same Apple Account. Each device picks up the activity and renders it using the layout you provide for that form
factor.
Note: The multiplatform broadcast happens automatically through iCloud. You do not push separately to each device. A single
Activity.requestorActivity.updatecall propagates everywhere.
To opt into the new surfaces, you declare supported platforms in your widget extension’s Info.plist or through the
ActivityConfiguration initializer:
@available(iOS 26, *)
struct DeliveryActivityConfiguration: Widget {
var body: some WidgetConfiguration {
ActivityConfiguration(for: DeliveryAttributes.self) { context in
// Lock Screen and banner presentation
DeliveryLockScreenView(state: context.state)
} dynamicIsland: { context in
DynamicIsland {
DynamicIslandExpandedRegion(.leading) {
Label(
context.state.currentStatus,
systemImage: "box.truck"
)
}
DynamicIslandExpandedRegion(.trailing) {
Text(context.state.estimatedArrival, style: .timer)
}
DynamicIslandExpandedRegion(.bottom) {
Text("Driver: \(context.state.driverName)")
}
} compactLeading: {
Image(systemName: "box.truck")
} compactTrailing: {
Text(context.state.estimatedArrival, style: .timer)
} minimal: {
Image(systemName: "box.truck")
}
}
.supplementalActivityFamilies([.carPlay, .medium, .small])
}
}
The .supplementalActivityFamilies modifier is the new iOS 26 API that opts your activity into additional form factors.
Without it, your activity renders only on iPhone — the same behavior as iOS 18.
Apple Docs:
ActivityConfiguration— ActivityKit
WWDC Reference
Apple introduced these changes at WWDC25 in the session “Bring Live Activities to more places” (WWDC25-10228). The session walks through the platform expansion, layout family system, and best practices for each surface.
Adapting Layouts per Form Factor
A single SwiftUI view cannot look right across a 2-inch Dynamic Island pill and a CarPlay dashboard tile. iOS 26
introduces ActivityViewContext.activityFamily to let you branch layouts based on the rendering surface.
Reading the Activity Family
The activityFamily property on ActivityViewContext tells you which surface is rendering your view:
struct DeliveryLockScreenView: View {
let state: DeliveryAttributes.ContentState
@Environment(\.activityFamily) var activityFamily
var body: some View {
switch activityFamily {
case .medium:
// iPhone and iPad Lock Screen banner
DeliveryBannerView(state: state)
case .small:
// watchOS Smart Stack, macOS menu bar
DeliveryCompactView(state: state)
case .carPlay:
// CarPlay dashboard tile
DeliveryCarPlayView(state: state)
@unknown default:
DeliveryBannerView(state: state)
}
}
}
This pattern keeps each layout focused on its surface. Avoid the temptation to use a single adaptive layout with excessive conditionals — the constraints are different enough that separate views are cleaner.
CarPlay Layout Constraints
CarPlay activities must follow strict driver-safety guidelines. Apple enforces these at review time:
struct DeliveryCarPlayView: View {
let state: DeliveryAttributes.ContentState
var body: some View {
HStack(spacing: 12) {
Image(systemName: "box.truck.fill")
.font(.title2)
.foregroundStyle(.secondary)
VStack(alignment: .leading, spacing: 4) {
Text(state.currentStatus)
.font(.headline)
.lineLimit(1)
Text(state.estimatedArrival, style: .timer)
.font(.subheadline)
.monospacedDigit()
}
}
.padding()
}
}
Warning: CarPlay activities are limited to non-interactive, glanceable content. Do not include buttons, toggles, or any interactive elements. Apple will reject apps that distract the driver. Keep text to two lines maximum.
macOS Tahoe Menu Bar
On macOS Tahoe, Live Activities render in the menu bar as compact, always-visible indicators. The .small activity
family handles this surface. Think of it like a miniature version of your Lock Screen view — show the single most
important data point:
struct DeliveryCompactView: View {
let state: DeliveryAttributes.ContentState
var body: some View {
Label {
Text(state.estimatedArrival, style: .timer)
.monospacedDigit()
} icon: {
Image(systemName: "box.truck")
}
.font(.caption)
}
}
Users can click the menu bar item to expand it into a richer popover — that popover uses your .medium layout. So the
.medium view does double duty: iPhone Lock Screen and macOS expanded popover.
iPad Considerations
iPadOS 26 renders Live Activities on the Lock Screen and in a floating island similar to Dynamic Island on iPhone. The
key difference is width: iPad activities have more horizontal space, especially in landscape. Use the .medium family
for iPad and rely on SwiftUI’s flexible layout system to fill the extra width gracefully.
Tip: Test your
.mediumlayout at both iPhone and iPad widths. Use the Xcode preview canvas with different device targets to catch truncation or awkward spacing before it ships.
Scheduled and Time-Bound Activities
iOS 26 adds two new activity lifecycle modes beyond the existing real-time push model: scheduled activities and time-bound activities.
Scheduled Activities
A scheduled activity starts at a future date without requiring your app to be running. This is ideal for events with a known start time — a Pixar movie premiere, a rocket launch countdown, a sports match kickoff:
let attributes = MoviePremiereAttributes(
movieTitle: "Toy Story 5",
theaterName: "El Capitan Theatre"
)
let premiereState = MoviePremiereAttributes.ContentState(
doorsOpen: false,
countdownMessage: "Arriving soon..."
)
let activity = try Activity.request(
attributes: attributes,
content: .init(state: premiereState, staleDate: nil),
scheduledDate: Calendar.current.date(
from: DateComponents(
year: 2026, month: 6, day: 20,
hour: 18, minute: 0
)
)!
)
The system wakes your activity at the scheduled time and renders it across all opted-in devices. Before the scheduled date, the activity exists but is not visible to the user.
Note: Scheduled activities still count against the system’s per-app activity limit. Do not schedule dozens of activities hoping they will queue up — the system will reject requests beyond the limit.
Time-Bound Activities
A time-bound activity has a defined end time. When the time expires, the system automatically transitions the activity
to its .ended state without requiring an explicit Activity.end() call from your app:
// A 90-minute rendering job for the next Pixar short
let renderAttributes = RenderJobAttributes(
projectName: "Luca: Portorosso Cup",
renderer: "RenderMan 26"
)
let renderState = RenderJobAttributes.ContentState(
framesCompleted: 0,
totalFrames: 14400,
currentScene: "Opening Shot"
)
let activity = try Activity.request(
attributes: renderAttributes,
content: .init(state: renderState, staleDate: nil),
timeBound: .init(
endDate: .now.addingTimeInterval(90 * 60),
dismissalPolicy: .afterTimeout(TimeInterval(5 * 60))
)
)
The dismissalPolicy controls how long the ended activity lingers on screen before the system removes it.
.afterTimeout keeps it visible for a grace period; .immediate removes it the moment it ends.
Advanced Usage
Updating Activities Across Platforms
When you call Activity.update(), the update propagates to every device rendering the activity. But there is a
subtlety: updates are best-effort on secondary devices. The iPhone is the source of truth, and other devices sync
through iCloud with a small delay (typically under 2 seconds on a good network, but potentially longer).
// Update from your app or a push notification handler
let updatedState = DeliveryAttributes.ContentState(
currentStatus: "Delivered to Andy's room",
estimatedArrival: .now,
driverName: "Al McWhiggin"
)
await activity.update(
ActivityContent(state: updatedState, staleDate: nil),
alertConfiguration: AlertConfiguration(
title: "Package Delivered!",
body: "Your Buzz Lightyear has arrived.",
sound: .default
)
)
The alertConfiguration parameter triggers a notification on devices where the activity is visible. On CarPlay, this
appears as an audio chime and a brief banner. On macOS, it is a standard notification. On Watch, it triggers a haptic
tap.
Warning: Do not rely on sub-second update propagation to secondary devices. If your activity shows a real-time timer, use
Text(_:style: .timer)orText(_:style: .relative)which count down locally on each device rather than depending on network-synced state.
Push Token Per Platform
In iOS 26, Activity.pushToken remains the single token for your activity, but the system routes push updates to all
devices internally. You do not need separate tokens per platform. Your server sends one push notification, and
ActivityKit handles fan-out:
for await tokenData in activity.pushTokenUpdates {
let token = tokenData.map {
String(format: "%02x", $0)
}.joined()
// Send this single token to your server
// System handles delivery to iPhone, Mac, iPad, CarPlay, Watch
await sendTokenToServer(token, activityID: activity.id)
}
Handling Platform-Specific End States
When ending an activity, you may want different final content per platform. Use the activity family environment value in your dismissed content view:
let finalState = DeliveryAttributes.ContentState(
currentStatus: "Delivered",
estimatedArrival: .now,
driverName: "Al McWhiggin"
)
await activity.end(
ActivityContent(state: finalState, staleDate: nil),
dismissalPolicy: .afterTimeout(TimeInterval(10 * 60))
)
The final content view reads activityFamily just like the active view, so your DeliveryLockScreenView switch
statement handles the ended state per platform automatically. On macOS, the menu bar item fades out after the dismissal
timeout. On CarPlay, it slides off the dashboard.
Performance Considerations
Live Activities across multiple platforms mean more frequent SwiftUI view evaluations. Each device renders your view independently, but the update payload travels through iCloud sync, which has implications:
Update frequency. Apple recommends no more than one update per second for real-time activities, and that guidance has not changed. But now each update triggers re-renders on up to five surfaces. Keep your view bodies lightweight — avoid expensive computed properties or complex layout calculations in the activity view.
Payload size. The ContentState is serialized and synced across devices. Keep it under 4 KB (Apple’s documented
limit). Resist the temptation to stuff your entire model into the content state. Include only the data you need to
render the view:
// Simplified for clarity
struct ContentState: Codable, Hashable {
// Good: minimal, render-focused data
var statusText: String
var progress: Double
var estimatedEnd: Date
// Bad: full model objects, images, or large collections
// var allRenderFrames: [FrameData] — Do not do this
}
Stale dates. Use staleDate aggressively. If your activity shows a countdown, set the stale date to the expected
arrival time. The system dims stale activities on all platforms, signaling to the user that the data might not be
current. This is especially important on macOS and CarPlay where the activity is always visible and stale data is
immediately noticeable.
Apple Docs:
Activity— ActivityKit
Profiling. Use Instruments with the “WidgetKit” template to profile your activity’s view body evaluation time. On secondary devices, you cannot attach Instruments directly, but you can test each layout in Xcode previews and simulator to catch performance regressions.
When to Use (and When Not To)
| Scenario | Recommendation |
|---|---|
| Real-time delivery or ride-share tracking | Use multiplatform Live Activities. The canonical use case. |
| Sports scores and live event tracking | Use scheduled activities. Set the date to kickoff time. |
| Long-running background tasks | Use time-bound activities with estimated end date. |
| Persistent status indicators | Prefer WidgetKit widgets. Apple rejects persistent activities. |
| Content requiring user interaction | Use CarPlay templates or interactive widgets instead. |
| Apps targeting iOS 17 or earlier | Stick with iPhone-only APIs. Use @available(iOS 26, *). |
The decision framework is straightforward: if the information is temporal (it starts, updates, and ends within hours) and glanceable (understood in under 3 seconds), it belongs in a Live Activity. The iOS 26 expansion does not change this — it just means the right information now appears on the right device at the right time.
Summary
- iOS 26 extends Live Activities to CarPlay, macOS Tahoe, iPadOS 26, and watchOS 12 through a single
Activity.requestcall with automatic iCloud sync across devices. - Use
.supplementalActivityFamilieson yourActivityConfigurationto opt into new surfaces, and branch layouts using theactivityFamilyenvironment value. - Scheduled activities start at a future date without your app running. Time-bound activities end automatically when their duration expires.
- Keep
ContentStateunder 4 KB, useText(_:style: .timer)for local countdowns, and test every layout at each platform’s width constraints. - CarPlay activities are strictly non-interactive and subject to driver-safety review guidelines.
The multiplatform expansion makes Live Activities one of the highest-impact features you can adopt in iOS 26. If you already have a working Live Activity, adding CarPlay and Mac support is a matter of layout work, not architectural change. For the full WidgetKit story — push reloads, Liquid Glass rendering, and RelevanceKit — see WidgetKit in iOS 26.