home / skills / rshankras / claude-code-apple-skills / widget-generator
npx playbooks add skill rshankras/claude-code-apple-skills --skill widget-generatorReview the files below or copy the command above to add this skill to your agents.
---
name: widget-generator
description: Generate WidgetKit widgets for iOS/macOS home screen and lock screen with timeline providers, interactive elements, and App Intent configuration. Use when adding widgets to an app.
allowed-tools: [Read, Write, Edit, Glob, Grep, Bash, AskUserQuestion]
---
# Widget Generator
Generate a complete WidgetKit widget implementation with timeline providers, size-specific views, lock screen accessory widgets, interactive elements (iOS 17+), and App Intent configuration.
## When This Skill Activates
Use this skill when the user:
- Asks to "add widgets" or "add a widget" to their app
- Mentions "WidgetKit" or "home screen widgets"
- Wants "lock screen widgets" or "accessory widgets"
- Asks about "widget timelines" or "timeline providers"
- Wants "interactive widgets" with buttons or toggles
- Mentions "widget configuration" or "configurable widgets"
- Asks about "App Intent widgets" or "AppIntentConfiguration"
- Wants to show data on the home screen or lock screen
## Pre-Generation Checks
### 1. Project Context Detection
- [ ] Check for existing widget extension target
- [ ] Check for an existing `WidgetBundle`
- [ ] Verify deployment target (iOS 17+ recommended for interactive widgets)
- [ ] Identify source file locations and project structure
### 2. Conflict Detection
Search for existing widget code:
```
Glob: **/*Widget*.swift, **/*TimelineProvider*.swift
Grep: "WidgetKit" or "TimelineProvider" or "WidgetBundle" or "WidgetConfiguration"
```
If an existing widget extension is found:
- Ask if the new widget should be added to the existing widget extension
- Check for an existing `WidgetBundle` to extend
If a `WidgetBundle` already exists:
- Add the new widget to the existing bundle instead of creating a new one
- Do NOT create a second `@main` entry point
If widget code with the same name exists:
- Ask user whether to replace or rename
### 3. Required Capabilities
**Widgets require:**
- A widget extension target (File > New > Target > Widget Extension)
- App Groups capability if sharing data between the main app and widget
- iOS 14+ for basic widgets, iOS 16+ for lock screen, iOS 17+ for interactive widgets
## Configuration Questions
Ask user via AskUserQuestion:
1. **What is this widget for?** (freeform)
- Examples: weather forecast, task list, fitness stats, quick actions, countdown timer
- This determines the data model and timeline update strategy
2. **Which widget sizes should be supported?**
- Home screen: systemSmall, systemMedium, systemLarge, systemExtraLarge (iPad only)
- Lock screen: accessoryCircular, accessoryRectangular, accessoryInline
- All home screen sizes
- All home screen + lock screen sizes (recommended)
3. **What type of widget?**
- **Static** (`StaticConfiguration`) -- content updated on a schedule, no user configuration
- **Configurable** (`AppIntentConfiguration`, iOS 17+) -- user can choose what the widget displays via long-press edit
- **Interactive** (`AppIntentConfiguration` + `Button`/`Toggle`, iOS 17+) -- user can tap buttons or toggles directly on the widget
4. **What is the data source?**
- Local data (UserDefaults, SwiftData, Core Data)
- Shared data via App Groups (main app writes, widget reads)
- Network API (fetched during timeline refresh)
- Combination of local and network
5. **How often should the widget update?**
- Every 15 minutes (minimum practical interval)
- Every 30 minutes
- Every hour (recommended default)
- A few times per day
- Based on specific times (e.g., calendar events)
- On-demand from the main app via `WidgetCenter.shared.reloadTimelines(ofKind:)`
## Generation Process
### Step 1: Determine File Locations
Check project structure:
- If a widget extension target directory exists, add view and provider files there
- Otherwise, instruct user to create a Widget Extension target first
For widget extension files:
- Place inside the existing widget extension directory (e.g., `MyAppWidgets/`)
For shared data models (if using App Groups):
- If `Sources/` or `Shared/` exists --> place there
- Otherwise --> create alongside existing models
### Step 2: Create Core Files
Generate these files based on configuration answers:
1. **`{Name}Widget.swift`** -- Widget definition with configuration
- `Widget` struct with `StaticConfiguration` or `AppIntentConfiguration`
- Supported families declaration
- Display name and description
2. **`{Name}TimelineProvider.swift`** -- Timeline logic
- `TimelineProvider` (static) or `AppIntentTimelineProvider` (configurable)
- Placeholder, snapshot, and timeline methods
3. **`{Name}Entry.swift`** -- Timeline entry model
- `TimelineEntry` struct with date and display data
4. **`{Name}WidgetViews.swift`** -- Size-specific views
- Separate view struct for each supported family
- Uses `containerBackground` for iOS 17+ removable backgrounds
5. **`{Name}AppIntent.swift`** (if interactive or configurable)
- `WidgetConfigurationIntent` for configurable widgets
- `AppIntent` for interactive widget buttons/toggles
6. **WidgetBundle update** -- Register the new widget
- Add to existing bundle or create new one
### Step 3: Generate Code from Templates
Use the templates in **templates.md** and customize based on user answers:
- Replace placeholder names with the actual widget name
- Configure supported families based on size selection
- Include or exclude interactive elements
- Include or exclude lock screen accessory views
- Set up App Group shared data access if needed
- Configure timeline refresh policy based on update frequency
## Output Format
After generation, provide:
### Files Created
```
MyAppWidgets/
├── {Name}Widget.swift # Widget definition + configuration
├── {Name}TimelineProvider.swift # Timeline provider with placeholder/snapshot/timeline
├── {Name}Entry.swift # TimelineEntry data model
├── {Name}WidgetViews.swift # Size-specific views for each family
├── {Name}AppIntent.swift # (if configurable/interactive) App Intent
└── (update WidgetBundle if needed)
Shared/
└── {Name}DataProvider.swift # (if App Groups) Shared data access
```
### Integration Steps
**1. Add the widget extension target (if not present):**
- File > New > Target > Widget Extension
- Choose "Include Configuration App Intent" if configurable
- Ensure the widget extension embeds in the main app
**2. Enable App Groups (if sharing data with the main app):**
- Select the main app target > Signing & Capabilities > + Capability > App Groups
- Select the widget extension target > Signing & Capabilities > + Capability > App Groups
- Use the same group identifier (e.g., `group.com.yourcompany.yourapp`)
**3. Register the widget in the WidgetBundle:**
```swift
@main
struct MyAppWidgets: WidgetBundle {
var body: some Widget {
// Existing widgets...
{Name}Widget()
}
}
```
**4. Trigger widget updates from the main app when data changes:**
```swift
import WidgetKit
// Reload a specific widget
WidgetCenter.shared.reloadTimelines(ofKind: "{Name}Widget")
// Or reload all widgets
WidgetCenter.shared.reloadAllTimelines()
```
**5. For App Group data sharing, write from the main app:**
```swift
let sharedDefaults = UserDefaults(suiteName: "group.com.yourcompany.yourapp")
sharedDefaults?.set(encodedData, forKey: "widgetData")
// Then trigger reload
WidgetCenter.shared.reloadTimelines(ofKind: "{Name}Widget")
```
### Testing Instructions
1. **Simulator support:** Widgets can be previewed in Xcode Canvas and tested in Simulator.
2. **Add to home screen:** Long-press the home screen > tap "+" > find your app > select the widget size.
3. **Lock screen widgets:** Long-press the lock screen > "Customize" > select the widget area.
4. **Preview in Xcode:** Use `#Preview` with timeline entry data for rapid iteration.
5. **Timeline debugging:** Use `WidgetCenter.shared.getCurrentConfigurations` to verify registered widgets.
6. **Interactive widget testing (iOS 17+):** Tap buttons/toggles directly on the widget in Simulator or device.
7. **Memory profiling:** Widgets have a 40MB memory limit. Profile in Instruments if loading images or large datasets.
## Common Widget Patterns
### Weather Widget
- **Data:** Temperature, condition icon, hourly forecast
- **Sizes:** systemSmall (current temp), systemMedium (hourly), accessoryCircular (temp gauge)
- **Update:** Every 30 minutes via network API
- **Timeline:** Generate entries for next few hours with forecast data
### Calendar / Events Widget
- **Data:** Upcoming events, times, locations
- **Sizes:** systemSmall (next event), systemMedium (next 3 events), accessoryRectangular (next event)
- **Update:** Based on event start times using `.after(nextEventDate)` policy
- **Timeline:** One entry per upcoming event transition
### Fitness / Health Widget
- **Data:** Steps, calories, activity rings
- **Sizes:** systemSmall (ring summary), accessoryCircular (ring gauge), accessoryRectangular (stats)
- **Update:** Every 15-30 minutes from HealthKit via App Groups
- **Interactive:** None (read-only data display)
### Quick Actions Widget
- **Data:** Action buttons (start timer, toggle light, log water)
- **Sizes:** systemSmall (single action), systemMedium (2-4 actions)
- **Update:** Infrequent (actions are static, only state changes)
- **Interactive:** `Button(intent:)` for each action (iOS 17+)
### Countdown Widget
- **Data:** Target date, label, time remaining
- **Sizes:** systemSmall (days remaining), accessoryCircular (days number), accessoryInline (text countdown)
- **Update:** Daily or use `Text(date, style: .timer)` / `Text(date, style: .relative)` for automatic live updates
- **Timeline:** SwiftUI date styles update automatically without timeline refreshes
## Gotchas and Limits
- **Timeline budget:** The system limits how often your timeline provider runs. Typically ~40-70 refreshes per day. Do not rely on exact timing.
- **40MB memory limit:** Widget extensions are killed if they exceed 40MB. Avoid loading large images or datasets. Use thumbnails and minimal data.
- **`containerBackground` required (iOS 17+):** All widget views must use `.containerBackground(for: .widget)` to support the system's removable background feature. Without this, widgets show a default placeholder background.
- **Accessory family rendering:** Lock screen widgets render in a limited color space. Use `AccessoryWidgetBackground()` for backgrounds and keep designs simple with high contrast.
- **No animation:** Widgets do not support explicit animations. Use `Text(date, style: .timer)` for countdowns; the system animates these for you.
- **No scrolling:** Widgets cannot scroll. Design for fixed, visible content.
- **No video or maps:** MapKit and AVKit are not available in widget extensions.
- **Networking in timeline provider:** Network requests in `getTimeline` must complete quickly. The system may terminate long-running providers.
- **`@main` conflict:** Only one `@main` per widget extension. If you have multiple widgets, use a `WidgetBundle` as the single `@main` entry point.
- **Configurable widget data persistence:** `AppIntent` parameter values are stored by the system. Do not rely on UserDefaults for configuration state.
- **Xcode previews:** Use `#Preview(as: .systemSmall)` for family-specific widget previews.
- **Shared code with main app:** Timeline entries and data models referenced by both targets must have target membership in both the main app and the widget extension.
## References
- **templates.md** -- Production-ready code templates for widget definition, timeline provider, views, and App Intents
- [WidgetKit Documentation](https://developer.apple.com/documentation/widgetkit)
- [Creating a Widget Extension](https://developer.apple.com/documentation/widgetkit/creating-a-widget-extension)
- [Making a Configurable Widget](https://developer.apple.com/documentation/widgetkit/making-a-configurable-widget)
- [Adding Interactivity to Widgets](https://developer.apple.com/documentation/widgetkit/adding-interactivity-to-widgets-and-live-activities)
- [WidgetKit Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/widgets)