widgetkit
SKILL.md
WidgetKit Development Skill
Build glanceable, timely experiences across Apple platforms using WidgetKit.
Quick Start
Create Widget Extension
- File → New → Target → Widget Extension
- Deselect "Include Live Activity" and "Include Configuration App Intent" for static widgets
- Widget requires:
Widgetprotocol,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
| Family | Platforms | Use Case |
|---|---|---|
systemSmall |
iOS, iPadOS, macOS, visionOS | Single tap target, glanceable info |
systemMedium |
iOS, iPadOS, macOS, visionOS | Multiple data points, interactive elements |
systemLarge |
iOS, iPadOS, macOS, visionOS | Rich content, multiple interactions |
systemExtraLarge |
iPadOS, macOS, visionOS | Dashboard-style layouts |
accessoryCircular |
iOS Lock Screen, watchOS | Minimal info, gauge-style |
accessoryRectangular |
iOS Lock Screen, watchOS | 2-3 lines of text |
accessoryInline |
iOS Lock Screen, watchOS | Single line text + optional image |
accessoryCorner |
watchOS only | Corner 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:
| Mode | When Used | Behavior |
|---|---|---|
fullColor |
Home Screen (iOS 17-), macOS desktop | Full color preserved |
accented |
Home Screen tinted/clear, visionOS, watchOS | Divides into accent + primary groups |
vibrant |
Lock Screen, StandBy | Desaturated, 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
| Type | Use Case |
|---|---|
StaticConfiguration |
No user configuration needed |
AppIntentConfiguration |
User-configurable (iOS 17+) |
ActivityConfiguration |
Live Activities |
Reference Documentation
- Timeline & Updates: Timeline providers, reload policies, push updates
- Interactivity: Buttons, toggles, App Intents, deep links
- Live Activities: ActivityKit, Dynamic Island, Lock Screen
- Design Guidelines: HIG best practices, layout, typography
- Platform Specifics: iOS, watchOS, macOS, visionOS differences
- Dimensions: Exact sizes for all widget families per device
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
Weekly Installs
1
Repository
eyadkelleh/carp…de_skillGitHub Stars
5
First Seen
6 days ago
Security Audits
Installed on
zencoder1
amp1
cline1
openclaw1
opencode1
cursor1