widgetkit

Build iOS/macOS/watchOS/visionOS widgets, Live Activities, watch complications, and controls using Apple's WidgetKit framework. Use when creating widget extensions, timeline providers, configurable widgets, Lock Screen widgets, Smart Stack widgets, Live Activities with ActivityKit, interactive widgets with buttons/toggles, or watch complications. Covers all widget families (systemSmall/Medium/Large/ExtraLarge, accessoryCircular/Rectangular/Inline/Corner) and rendering modes.

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 "widgetkit" with this command: npx skills add ios-agent/iosagent.dev/ios-agent-iosagent-dev-widgetkit

WidgetKit Development Skill

Build glanceable, timely experiences across Apple platforms using WidgetKit.

Quick Start

Create Widget Extension

  1. File → New → Target → Widget Extension
  2. Deselect "Include Live Activity" and "Include Configuration App Intent" for static widgets
  3. Widget requires: Widget protocol, TimelineProvider, and SwiftUI views

Minimal Widget Structure

@main
struct MyWidget: Widget {
    var body: some WidgetConfiguration {
        StaticConfiguration(
            kind: "com.app.mywidget",
            provider: Provider()
        ) { entry in
            MyWidgetView(entry: entry)
        }
        .configurationDisplayName("My Widget")
        .description("Shows key information")
        .supportedFamilies([.systemSmall, .systemMedium])
    }
}

struct Provider: TimelineProvider {
    func placeholder(in context: Context) -> SimpleEntry {
        SimpleEntry(date: .now)
    }
    
    func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> Void) {
        completion(SimpleEntry(date: .now))
    }
    
    func getTimeline(in context: Context, completion: @escaping (Timeline<SimpleEntry>) -> Void) {
        let entry = SimpleEntry(date: .now)
        let nextUpdate = Calendar.current.date(byAdding: .minute, value: 15, to: .now)!
        completion(Timeline(entries: [entry], policy: .after(nextUpdate)))
    }
}

struct SimpleEntry: TimelineEntry {
    let date: Date
}

Widget Families & Sizes

FamilyPlatformsUse Case
systemSmalliOS, iPadOS, macOS, visionOSSingle tap target, glanceable info
systemMediumiOS, iPadOS, macOS, visionOSMultiple data points, interactive elements
systemLargeiOS, iPadOS, macOS, visionOSRich content, multiple interactions
systemExtraLargeiPadOS, macOS, visionOSDashboard-style layouts
accessoryCirculariOS Lock Screen, watchOSMinimal info, gauge-style
accessoryRectangulariOS Lock Screen, watchOS2-3 lines of text
accessoryInlineiOS Lock Screen, watchOSSingle line text + optional image
accessoryCornerwatchOS onlyCorner complications

Adapt to Widget Family

struct MyWidgetView: View {
    @Environment(\.widgetFamily) var family
    
    var body: some View {
        switch family {
        case .systemSmall: CompactView()
        case .systemMedium: MediumView()
        case .systemLarge: DetailedView()
        case .accessoryCircular: GaugeView()
        case .accessoryRectangular: RectangularView()
        default: CompactView()
        }
    }
}

Rendering Modes

Widgets render differently based on context:

ModeWhen UsedBehavior
fullColorHome Screen (iOS 17-), macOS desktopFull color preserved
accentedHome Screen tinted/clear, visionOS, watchOSDivides into accent + primary groups
vibrantLock Screen, StandByDesaturated, blurred effect
@Environment(\.widgetRenderingMode) var renderingMode

var body: some View {
    switch renderingMode {
    case .fullColor: FullColorView()
    case .accented: AccentedView()
    case .vibrant: VibrantView()
    @unknown default: FullColorView()
    }
}

Interactivity

Buttons & Toggles (iOS 17+)

Button(intent: RefreshIntent()) {
    Label("Refresh", systemImage: "arrow.clockwise")
}

Toggle(isOn: $isEnabled, intent: ToggleIntent()) {
    Text("Enable")
}

Deep Links

MyWidgetView()
    .widgetURL(URL(string: "myapp://detail/123")!)

// Or for multiple links in larger widgets:
Link(destination: URL(string: "myapp://item/1")!) {
    ItemView()
}

Configuration Types

TypeUse Case
StaticConfigurationNo user configuration needed
AppIntentConfigurationUser-configurable (iOS 17+)
ActivityConfigurationLive Activities

Reference Documentation

Key Constraints

  • No real-time updates: Use timelines; system batches updates
  • Limited budget: ~40-70 refreshes/day depending on usage
  • No continuous animations: Only transition animations up to 2 seconds
  • SwiftUI only: UIKit views not supported
  • Stateless: No persistent state between renders
  • No network in views: Fetch data in timeline provider only

Common Patterns

Share Data with Main App

// Use App Groups
let sharedDefaults = UserDefaults(suiteName: "group.com.app.shared")

// Or shared container
let containerURL = FileManager.default.containerURL(
    forSecurityApplicationGroupIdentifier: "group.com.app.shared"
)

Force Widget Refresh

import WidgetKit
WidgetCenter.shared.reloadTimelines(ofKind: "com.app.mywidget")
WidgetCenter.shared.reloadAllTimelines()

Placeholder for Sensitive Content

.privacySensitive() // Redacts when device locked

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.

Coding

swiftui

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

metal-gpu

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

carplay

No summary provided by upstream source.

Repository SourceNeeds Review