widgets-live-activities

WidgetKit and Live Activities

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "widgets-live-activities" with this command: npx skills add bluewaves-creations/bluewaves-skills/bluewaves-creations-bluewaves-skills-widgets-live-activities

WidgetKit and Live Activities

Comprehensive guide to WidgetKit for Home Screen widgets, Live Activities, Dynamic Island, and Control Center integration in iOS 26.

Prerequisites

  • iOS 17+ for interactive widgets (iOS 26 recommended)

  • Xcode 26+

  • Widget Extension target

Widget Extension Setup

Creating Widget Target

  • File → New → Target

  • Select "Widget Extension"

  • Name your widget

  • Enable "Include Live Activity" if needed

  • Enable "Include Configuration App Intent" for configurable widgets

Basic Widget Structure

import WidgetKit import SwiftUI

struct SimpleWidget: Widget { let kind: String = "SimpleWidget"

var body: some WidgetConfiguration {
    StaticConfiguration(kind: kind, provider: Provider()) { entry in
        SimpleWidgetEntryView(entry: entry)
            .containerBackground(.fill.tertiary, for: .widget)
    }
    .configurationDisplayName("My Widget")
    .description("Shows important information")
    .supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
}

}

Timeline Provider

struct Provider: TimelineProvider { func placeholder(in context: Context) -> SimpleEntry { SimpleEntry(date: Date(), message: "Loading...") }

func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> Void) {
    let entry = SimpleEntry(date: Date(), message: "Snapshot")
    completion(entry)
}

func getTimeline(in context: Context, completion: @escaping (Timeline<SimpleEntry>) -> Void) {
    var entries: [SimpleEntry] = []

    let currentDate = Date()
    for hourOffset in 0..<5 {
        let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)!
        let entry = SimpleEntry(date: entryDate, message: "Hour \(hourOffset)")
        entries.append(entry)
    }

    let timeline = Timeline(entries: entries, policy: .atEnd)
    completion(timeline)
}

}

struct SimpleEntry: TimelineEntry { let date: Date let message: String }

Widget View

struct SimpleWidgetEntryView: View { var entry: Provider.Entry

@Environment(\.widgetFamily) var family

var body: some View {
    switch family {
    case .systemSmall:
        SmallWidgetView(entry: entry)
    case .systemMedium:
        MediumWidgetView(entry: entry)
    case .systemLarge:
        LargeWidgetView(entry: entry)
    default:
        Text(entry.message)
    }
}

}

struct SmallWidgetView: View { let entry: SimpleEntry

var body: some View {
    VStack {
        Text(entry.message)
            .font(.headline)
        Text(entry.date, style: .time)
            .font(.caption)
    }
}

}

iOS 26 Glass Presentation

Accented Rendering

Widgets on iOS 26 use the new glass presentation system:

struct GlassWidget: Widget { var body: some WidgetConfiguration { StaticConfiguration(kind: "GlassWidget", provider: Provider()) { entry in GlassWidgetView(entry: entry) .containerBackground(.fill.tertiary, for: .widget) } .supportedFamilies([.systemSmall, .systemMedium]) } }

struct GlassWidgetView: View { let entry: SimpleEntry

var body: some View {
    VStack {
        Image(systemName: "star.fill")
            .font(.largeTitle)
            // Accented rendering for glass background
            .widgetAccentedRenderingMode(.accentedDesaturated)

        Text(entry.message)
            .font(.headline)
    }
}

}

Widget Accented Rendering Modes

// For images in widgets Image("CustomIcon") .widgetAccentedRenderingMode(.desaturated) // Blend with home screen .widgetAccentedRenderingMode(.accentedDesaturated) // Blend with accent .widgetAccentedRenderingMode(.fullColor) // Full color (media only)

Interactive Widgets

Button Actions

import AppIntents

struct InteractiveWidget: Widget { var body: some WidgetConfiguration { StaticConfiguration(kind: "Interactive", provider: Provider()) { entry in InteractiveWidgetView(entry: entry) .containerBackground(.fill.tertiary, for: .widget) } .supportedFamilies([.systemSmall, .systemMedium]) } }

struct InteractiveWidgetView: View { let entry: TaskEntry

var body: some View {
    VStack(alignment: .leading) {
        Text(entry.task.title)
            .font(.headline)

        Button(intent: ToggleTaskIntent(taskId: entry.task.id)) {
            Label(
                entry.task.isComplete ? "Completed" : "Mark Done",
                systemImage: entry.task.isComplete ? "checkmark.circle.fill" : "circle"
            )
        }
        .buttonStyle(.bordered)
    }
}

}

struct ToggleTaskIntent: AppIntent { static var title: LocalizedStringResource = "Toggle Task"

@Parameter(title: "Task ID")
var taskId: String

func perform() async throws -> some IntentResult {
    await TaskManager.shared.toggle(taskId)
    return .result()
}

}

Toggle Actions

struct ToggleWidgetView: View { let entry: SettingEntry

var body: some View {
    Toggle(isOn: entry.isEnabled, intent: ToggleSettingIntent(settingId: entry.id)) {
        Label("Enable Feature", systemImage: "gear")
    }
    .toggleStyle(.button)
}

}

Configurable Widgets

App Intent Configuration

import AppIntents

struct ConfigurableWidget: Widget { var body: some WidgetConfiguration { AppIntentConfiguration( kind: "ConfigurableWidget", intent: ConfigureWidgetIntent.self, provider: ConfigurableProvider() ) { entry in ConfigurableWidgetView(entry: entry) .containerBackground(.fill.tertiary, for: .widget) } .configurationDisplayName("Custom Widget") .description("Configure which data to show") .supportedFamilies([.systemSmall, .systemMedium]) } }

struct ConfigureWidgetIntent: WidgetConfigurationIntent { static var title: LocalizedStringResource = "Configure Widget" static var description = IntentDescription("Choose what to display")

@Parameter(title: "Category")
var category: WidgetCategory?

@Parameter(title: "Show Count")
var showCount: Bool

}

enum WidgetCategory: String, AppEnum { case recent, favorites, all

static var typeDisplayRepresentation = TypeDisplayRepresentation(name: "Category")
static var caseDisplayRepresentations: [WidgetCategory: DisplayRepresentation] = [
    .recent: "Recent",
    .favorites: "Favorites",
    .all: "All"
]

}

Configurable Provider

struct ConfigurableProvider: AppIntentTimelineProvider { func placeholder(in context: Context) -> ConfigurableEntry { ConfigurableEntry(date: Date(), configuration: ConfigureWidgetIntent()) }

func snapshot(for configuration: ConfigureWidgetIntent, in context: Context) async -> ConfigurableEntry {
    ConfigurableEntry(date: Date(), configuration: configuration)
}

func timeline(for configuration: ConfigureWidgetIntent, in context: Context) async -> Timeline<ConfigurableEntry> {
    let entry = ConfigurableEntry(date: Date(), configuration: configuration)
    return Timeline(entries: [entry], policy: .after(Date().addingTimeInterval(3600)))
}

}

struct ConfigurableEntry: TimelineEntry { let date: Date let configuration: ConfigureWidgetIntent }

Live Activities

Activity Attributes

import ActivityKit

struct DeliveryAttributes: ActivityAttributes { public struct ContentState: Codable, Hashable { var status: DeliveryStatus var estimatedArrival: Date var driverName: String }

var orderNumber: String
var restaurantName: String

}

enum DeliveryStatus: String, Codable { case preparing case onTheWay case arriving case delivered }

Live Activity View

struct DeliveryActivityView: View { let context: ActivityViewContext<DeliveryAttributes>

var body: some View {
    VStack(alignment: .leading, spacing: 8) {
        HStack {
            Text(context.attributes.restaurantName)
                .font(.headline)
            Spacer()
            Text(context.state.status.displayName)
                .font(.caption)
                .foregroundStyle(.secondary)
        }

        ProgressView(value: context.state.status.progress)

        HStack {
            Label(context.state.driverName, systemImage: "person.circle")
            Spacer()
            Text(context.state.estimatedArrival, style: .timer)
        }
        .font(.caption)
    }
    .padding()
}

}

Dynamic Island

struct DeliveryLiveActivity: Widget { var body: some WidgetConfiguration { ActivityConfiguration(for: DeliveryAttributes.self) { context in // Lock screen presentation DeliveryActivityView(context: context) } dynamicIsland: { context in DynamicIsland { // Expanded regions DynamicIslandExpandedRegion(.leading) { Image(systemName: "bag.fill") } DynamicIslandExpandedRegion(.trailing) { Text(context.state.estimatedArrival, style: .timer) } DynamicIslandExpandedRegion(.center) { Text(context.attributes.restaurantName) .font(.headline) } DynamicIslandExpandedRegion(.bottom) { ProgressView(value: context.state.status.progress) } } compactLeading: { Image(systemName: "bag.fill") } compactTrailing: { Text(context.state.estimatedArrival, style: .timer) } minimal: { Image(systemName: "bag.fill") } } } }

Starting a Live Activity

func startDeliveryActivity(order: Order) async throws { guard ActivityAuthorizationInfo().areActivitiesEnabled else { throw ActivityError.notAuthorized }

let attributes = DeliveryAttributes(
    orderNumber: order.id,
    restaurantName: order.restaurant
)

let initialState = DeliveryAttributes.ContentState(
    status: .preparing,
    estimatedArrival: order.estimatedArrival,
    driverName: "Assigning..."
)

let activity = try Activity.request(
    attributes: attributes,
    content: .init(state: initialState, staleDate: nil),
    pushType: .token  // Enable push updates
)

// Store activity ID for later updates
UserDefaults.standard.set(activity.id, forKey: "currentDeliveryActivity")

// Get push token for server updates
for await token in activity.pushTokenUpdates {
    let tokenString = token.map { String(format: "%02x", $0) }.joined()
    await sendTokenToServer(tokenString)
}

}

Updating Live Activity

func updateDeliveryStatus(to status: DeliveryStatus, driver: String? = nil) async { guard let activityId = UserDefaults.standard.string(forKey: "currentDeliveryActivity"), let activity = Activity<DeliveryAttributes>.activities.first(where: { $0.id == activityId }) else { return }

var newState = activity.content.state
newState.status = status
if let driver {
    newState.driverName = driver
}

await activity.update(
    ActivityContent(state: newState, staleDate: nil)
)

}

Ending Live Activity

func endDeliveryActivity() async { guard let activityId = UserDefaults.standard.string(forKey: "currentDeliveryActivity"), let activity = Activity<DeliveryAttributes>.activities.first(where: { $0.id == activityId }) else { return }

let finalState = DeliveryAttributes.ContentState(
    status: .delivered,
    estimatedArrival: Date(),
    driverName: activity.content.state.driverName
)

await activity.end(
    ActivityContent(state: finalState, staleDate: nil),
    dismissalPolicy: .after(.now + 3600)  // Dismiss after 1 hour
)

UserDefaults.standard.removeObject(forKey: "currentDeliveryActivity")

}

iOS 26 Live Activity Updates

CarPlay Support

Live Activities now appear on CarPlay in iOS 26:

struct CarPlayDeliveryActivity: Widget { var body: some WidgetConfiguration { ActivityConfiguration(for: DeliveryAttributes.self) { context in // Lock screen DeliveryActivityView(context: context) } dynamicIsland: { context in // Dynamic Island config... } .supplementalActivityFamilies([.small]) // CarPlay support } }

macOS Support

Live Activities now work on macOS Tahoe:

#if os(macOS) struct MacDeliveryActivity: Widget { var body: some WidgetConfiguration { ActivityConfiguration(for: DeliveryAttributes.self) { context in MacDeliveryView(context: context) } } } #endif

Control Center Widgets

Control Widget

import WidgetKit import AppIntents

struct QuickToggleControl: ControlWidget { var body: some ControlWidgetConfiguration { StaticControlConfiguration(kind: "QuickToggle") { ControlWidgetToggle( "Dark Mode", isOn: DarkModeBinding(), action: ToggleDarkModeIntent() ) { isOn in Label(isOn ? "On" : "Off", systemImage: isOn ? "moon.fill" : "sun.max") } } .displayName("Dark Mode") .description("Toggle dark mode") } }

struct DarkModeBinding: ControlValueProvider { var previewValue: Bool { false }

func currentValue() async throws -> Bool {
    await SettingsManager.shared.isDarkMode
}

}

struct ToggleDarkModeIntent: SetValueIntent { static var title: LocalizedStringResource = "Toggle Dark Mode"

@Parameter(title: "Dark Mode")
var value: Bool

func perform() async throws -> some IntentResult {
    await SettingsManager.shared.setDarkMode(value)
    return .result()
}

}

Control Widget Button

struct QuickActionControl: ControlWidget { var body: some ControlWidgetConfiguration { StaticControlConfiguration(kind: "QuickAction") { ControlWidgetButton(action: QuickNoteIntent()) { Label("Quick Note", systemImage: "note.text.badge.plus") } } .displayName("Quick Note") .description("Create a quick note") } }

Widget Relevance (watchOS 26)

Relevance Configuration

struct RelevantWidget: Widget { var body: some WidgetConfiguration { StaticConfiguration(kind: "Relevant", provider: RelevantProvider()) { entry in RelevantWidgetView(entry: entry) } .supportedFamilies([.accessoryRectangular]) } }

struct RelevantProvider: TimelineProvider { func relevances() async -> WidgetRelevances<Void> { // Define when widget is most relevant return WidgetRelevances( // Show during workout RelevantContext.workout: .defaultLarge, // Show in morning RelevantContext.date(from: morning, to: noon): .defaultMedium ) }

// Other provider methods...

}

Widget Data Sharing

App Groups

// In both app and widget extension let sharedDefaults = UserDefaults(suiteName: "group.com.yourapp.shared") sharedDefaults?.set(data, forKey: "widgetData")

// In widget let data = UserDefaults(suiteName: "group.com.yourapp.shared")?.data(forKey: "widgetData")

Shared Container

let containerURL = FileManager.default.containerURL( forSecurityApplicationGroupIdentifier: "group.com.yourapp.shared" )

Triggering Widget Refresh

import WidgetKit

// Refresh specific widget WidgetCenter.shared.reloadTimelines(ofKind: "MyWidget")

// Refresh all widgets WidgetCenter.shared.reloadAllTimelines()

Best Practices

  1. Efficient Timeline Updates

func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> Void) { // Generate only necessary entries let entries = generateRelevantEntries()

// Use appropriate refresh policy
let policy: TimelineReloadPolicy
if hasUpcomingEvents {
    policy = .after(nextEventDate)
} else {
    policy = .atEnd
}

completion(Timeline(entries: entries, policy: policy))

}

  1. Handle Widget Families

struct AdaptiveWidgetView: View { @Environment(.widgetFamily) var family

var body: some View {
    switch family {
    case .systemSmall:
        CompactView()
    case .systemMedium:
        MediumView()
    case .systemLarge, .systemExtraLarge:
        DetailedView()
    case .accessoryCircular:
        CircularView()
    case .accessoryRectangular:
        RectangularView()
    default:
        DefaultView()
    }
}

}

  1. Test with Widget Development Mode

// In scheme arguments -widgetKitDevMode YES

  1. Memory Efficiency

// Load images efficiently Image("icon") .resizable() .aspectRatio(contentMode: .fit)

// Avoid heavy computations in view body // Pre-calculate in provider

Official Resources

  • WidgetKit Documentation

  • ActivityKit Documentation

  • WWDC23: Bring widgets to new places

  • WWDC25: What's new in widgets

  • Adding interactivity to widgets

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

photographer-testino

No summary provided by upstream source.

Repository SourceNeeds Review
General

photographer-lindbergh

No summary provided by upstream source.

Repository SourceNeeds Review
General

photographer-lachapelle

No summary provided by upstream source.

Repository SourceNeeds Review
General

photographer-vonunwerth

No summary provided by upstream source.

Repository SourceNeeds Review