Skip to content

Displaying Live Activities (SwiftUI)

A SwiftUI guide for coding agents. Also covers activitykit lock screen widget, dynamic island widget, activityattributes contentstate, start update end live activity, live sports score widget.

ActivityKit (iOS 16.1) shows an ongoing activity on the lock screen and in the Dynamic Island. The activity is a WidgetKit widget without a timeline provider: the app starts, updates, and ends it.

Know the Limits First

The system ends a live activity after eight hours, so it fits matches and deliveries, not long flights. The dynamic state must stay under 4 KB. The app must be in the foreground to start one, and the app target's Info.plist needs the Supports Live Activities key set to YES.

Split the Data Into Static and Dynamic

Conform to ActivityAttributes. The attributes hold what never changes; the nested ContentState holds what updates.

import ActivityKit

struct FootballMatch: ActivityAttributes {
  typealias ContentState = Score

  struct Score: Codable, Hashable {
    let score1: Int
    let score2: Int
  }

  let team1: String
  let team2: String
}

Start, Update, End

Activity.request returns a handle. Keep it, then push new states or end with a dismissal policy. BackgroundTasks or push notifications can drive updates while the app is not in the foreground.

let activity = try Activity.request(
  attributes: FootballMatch(team1: "AJAX", team2: "PSV"),
  contentState: .init(score1: 0, score2: 0)
)

Task { await activity.update(using: .init(score1: 1, score2: 0)) }

Task {
  await activity.end(
    using: .init(score1: 1, score2: 0),
    dismissalPolicy: .immediate
  )
}

Present With ActivityConfiguration

The widget extension defines one ActivityConfiguration per attributes type: a lock screen view plus the four Dynamic Island slots (expanded regions, compact leading, compact trailing, minimal).

@available(iOSApplicationExtension 16.1, *)
struct FastingActivityWidget: Widget {
  var body: some WidgetConfiguration {
    ActivityConfiguration(for: FastingAttributes.self) { context in
      LiveActivityView(context: context)
        .padding(.horizontal)
    } dynamicIsland: { context in
      DynamicIsland {
        DynamicIslandExpandedRegion(.center) {
          LiveActivityView(context: context)
        }
      } compactLeading: {
        Image(systemName: "circle").foregroundColor(.green)
      } compactTrailing: {
        Text(verbatim: context.state.progress.formatted(.percent))
      } minimal: {
        Image(systemName: "circle").foregroundColor(.green)
      }
    }
  }
}

Add it to the widget bundle behind an availability check, next to the regular widgets.

@main struct FastBotWidgetBundle: WidgetBundle {
  var body: some Widget {
    FastBotWidget()
    if #available(iOS 16.1, *) {
      FastingActivityWidget()
    }
  }
}

Use this guidance in your coding agent

Install the Better Design MCP once. Your agent then loads this page with one call.

get-swiftui-guide({ topic: "swiftui-displaying-live-activities-in-ios16" })
claude mcp add --scope user better-design --transport http https://better-design.com/api/mcp --header "Authorization: Bearer <YOUR_API_KEY>"
Browse related design systems