Use when implementing widgets, Live Activities, Control Center controls, or app extensions - comprehensive API reference for WidgetKit, ActivityKit, App Groups, and extension lifecycle for iOS 14+
Provides comprehensive API reference for implementing iOS widgets, Live Activities, and Control Center controls.
npx claudepluginhub charleswiltgen/axiomThis skill inherits all available tools. When active, it can use any tool Claude has access to.
This skill provides comprehensive API reference for Apple's widget and extension ecosystem:
Widgets are SwiftUI archived snapshots rendered on a timeline by the system. Extensions are sandboxed executables bundled with your app.
✅ Use this skill when:
❌ Do NOT use this skill for:
For widgets that don't require user configuration.
@main
struct MyWidget: Widget {
let kind: String = "MyWidget"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider()) { entry in
MyWidgetEntryView(entry: entry)
}
.configurationDisplayName("My Widget")
.description("This widget displays...")
.supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
}
}
For widgets with user configuration using App Intents.
struct MyConfigurableWidget: Widget {
let kind: String = "MyConfigurableWidget"
var body: some WidgetConfiguration {
AppIntentConfiguration(
kind: kind,
intent: SelectProjectIntent.self,
provider: Provider()
) { entry in
MyWidgetEntryView(entry: entry)
}
.configurationDisplayName("Project Status")
.description("Shows your selected project")
}
}
Migration from IntentConfiguration: iOS 16 and earlier used IntentConfiguration with SiriKit intents. Migrate to AppIntentConfiguration for iOS 17+.
For Live Activities (covered in Live Activities section).
No user configuration needed? Use StaticConfiguration. Simple static options? Use AppIntentConfiguration with WidgetConfigurationIntent. Dynamic options from app data? Use AppIntentConfiguration + EntityQuery.
Quick Reference:
systemSmall (~170×170, iOS 14+) — Single piece of info, iconsystemMedium (~360×170, iOS 14+) — Multiple data points, chartsystemLarge (~360×380, iOS 14+) — Detailed view, listsystemExtraLarge (~720×380, iOS 15+ iPad only) — Rich layouts, multiple viewsaccessoryCircular (~48×48pt) — Circular complication, icon or gaugeaccessoryRectangular (~160×72pt) — Above clock, text + iconaccessoryInline (single line) — Above date, text onlystruct MyWidget: Widget {
var body: some WidgetConfiguration {
StaticConfiguration(kind: "MyWidget", provider: Provider()) { entry in
if #available(iOSApplicationExtension 16.0, *) {
switch entry.family {
case .systemSmall:
SmallWidgetView(entry: entry)
case .systemMedium:
MediumWidgetView(entry: entry)
case .accessoryCircular:
CircularWidgetView(entry: entry)
case .accessoryRectangular:
RectangularWidgetView(entry: entry)
default:
Text("Unsupported")
}
} else {
LegacyWidgetView(entry: entry)
}
}
.supportedFamilies([
.systemSmall,
.systemMedium,
.accessoryCircular,
.accessoryRectangular
])
}
}
Provides entries that define when the system should render your widget.
struct Provider: TimelineProvider {
// Placeholder while loading
func placeholder(in context: Context) -> SimpleEntry {
SimpleEntry(date: Date(), emoji: "😀")
}
// Shown in widget gallery
func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
let entry = SimpleEntry(date: Date(), emoji: "📷")
completion(entry)
}
// Actual timeline
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
var entries: [SimpleEntry] = []
let currentDate = Date()
// Create entry every hour for 5 hours
for hourOffset in 0 ..< 5 {
let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)!
let entry = SimpleEntry(date: entryDate, emoji: "⏰")
entries.append(entry)
}
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
}
}
Controls when the system requests a new timeline:
.atEnd — Reload after last entry.after(date) — Reload at specific date.never — No automatic reload (manual only)import WidgetKit
// Reload all widgets of this kind
WidgetCenter.shared.reloadAllTimelines()
// Reload specific kind
WidgetCenter.shared.reloadTimelines(ofKind: "MyWidget")
// ✅ GOOD: Strategic intervals (15-60 min)
let entries = (0..<8).map { offset in
let date = Calendar.current.date(byAdding: .minute, value: offset * 15, to: now)!
return SimpleEntry(date: date, data: data)
}
loadRecentItems(limit: 10), not entire database)Never make network requests in widget views — they won't complete before rendering. Fetch data in getTimeline() instead.
Complete getTimeline() in under 5 seconds. Cache expensive computations in the main app, read pre-computed data from shared container, limit to 10-20 entries.
Precompute everything in TimelineEntry, keep views simple. No expensive operations in body.
AsyncImage does NOT work in widgetsInteractive widgets use SwiftUI Button and Toggle with App Intents.
Button(intent: IncrementIntent()) {
Label("Increment", systemImage: "plus.circle")
}
The intent updates shared data via App Groups in its perform() method. See axiom-app-intents-ref for full AppIntent definition syntax.
Same pattern as Button — use a Toggle bound to state, invoke intent on change:
Toggle(isOn: $isEnabled) {
Text("Feature")
}
.onChange(of: isEnabled) { newValue in
Task { try? await ToggleFeatureIntent(enabled: newValue).perform() }
}
The intent follows the same AppIntent structure with a @Parameter(title: "Enabled") var enabled: Bool. See axiom-app-intents-ref for full AppIntent definition syntax.
Provides visual feedback during App Intent execution.
struct MyWidgetView: View {
var entry: Provider.Entry
var body: some View {
VStack {
Text(entry.status)
.invalidatableContent() // Dims during intent execution
Button(intent: RefreshIntent()) {
Image(systemName: "arrow.clockwise")
}
}
}
}
Effect: Content with .invalidatableContent() becomes slightly transparent while the associated intent executes, providing user feedback.
Text("\(entry.value)")
.contentTransition(.numericText(value: Double(entry.value)))
Effect: Numbers smoothly count up or down instead of instantly changing.
VStack {
if entry.showDetail {
DetailView()
.transition(.scale.combined(with: .opacity))
}
}
.animation(.spring(response: 0.3), value: entry.showDetail)
Define configuration parameters for your widget.
import AppIntents
struct SelectProjectIntent: WidgetConfigurationIntent {
static var title: LocalizedStringResource = "Select Project"
static var description = IntentDescription("Choose which project to display")
@Parameter(title: "Project")
var project: ProjectEntity?
// Provide default value
static var parameterSummary: some ParameterSummary {
Summary("Show \(\.$project)")
}
}
Provide dynamic options for configuration.
struct ProjectEntity: AppEntity {
var id: String
var name: String
static var typeDisplayRepresentation = TypeDisplayRepresentation(name: "Project")
var displayRepresentation: DisplayRepresentation {
DisplayRepresentation(title: "\(name)")
}
}
struct ProjectQuery: EntityQuery {
func entities(for identifiers: [String]) async throws -> [ProjectEntity] {
// Return projects matching these IDs
return await ProjectStore.shared.projects(withIDs: identifiers)
}
func suggestedEntities() async throws -> [ProjectEntity] {
// Return all available projects
return await ProjectStore.shared.allProjects()
}
}
struct Provider: AppIntentTimelineProvider {
func timeline(for configuration: SelectProjectIntent, in context: Context) async -> Timeline<SimpleEntry> {
let project = configuration.project // Use selected project
let entries = await generateEntries(for: project)
return Timeline(entries: entries, policy: .atEnd)
}
}
Defines static and dynamic data for a Live Activity.
import ActivityKit
struct PizzaDeliveryAttributes: ActivityAttributes {
// Static data - set when activity starts, never changes
struct ContentState: Codable, Hashable {
// Dynamic data - updated throughout activity lifecycle
var status: DeliveryStatus
var estimatedDeliveryTime: Date
var driverName: String?
}
// Static attributes
var orderNumber: String
var pizzaType: String
}
Key constraint: ActivityAttributes total data size must be under 4KB to start successfully.
import ActivityKit
let authorizationInfo = ActivityAuthorizationInfo()
let areActivitiesEnabled = authorizationInfo.areActivitiesEnabled
let attributes = PizzaDeliveryAttributes(
orderNumber: "12345",
pizzaType: "Pepperoni"
)
let initialState = PizzaDeliveryAttributes.ContentState(
status: .preparing,
estimatedDeliveryTime: Date().addingTimeInterval(30 * 60)
)
let activity = try Activity.request(
attributes: attributes,
content: ActivityContent(state: initialState, staleDate: nil),
pushType: nil // or .token for push notifications
)
Always check ActivityAuthorizationInfo().areActivitiesEnabled before requesting. Handle these errors from Activity.request():
ActivityAuthorizationError — User denied Live Activities permissionActivityError.dataTooLarge — ActivityAttributes exceeds 4KB; reduce attribute sizeActivityError.tooManyActivities — System limit reached (typically 2-3 simultaneous)Store activity.id after successful request for later updates.
// Find active activity by stored ID
guard let activity = Activity<PizzaDeliveryAttributes>.activities
.first(where: { $0.id == storedActivityID }) else { return }
let updatedState = PizzaDeliveryAttributes.ContentState(
status: .onTheWay,
estimatedDeliveryTime: Date().addingTimeInterval(10 * 60),
driverName: "John"
)
await activity.update(
ActivityContent(
state: updatedState,
staleDate: Date().addingTimeInterval(60) // Mark stale after 1 min
)
)
await activity.update(updatedContent, alertConfiguration: AlertConfiguration(
title: "Pizza is here!",
body: "Your \(attributes.pizzaType) pizza has arrived",
sound: .default
))
Use activity.activityStateUpdates async sequence to observe state changes (.active, .ended, .dismissed, .stale). Clean up stored activity IDs on .ended or .dismissed. Cancel the monitoring task in deinit.
await activity.end(
ActivityContent(state: finalState, staleDate: nil),
dismissalPolicy: .default
)
Dismissal policy options:
.immediate — Removes instantly.default — Stays on Lock Screen for ~4 hours.after(date) — Removes at specific time (e.g., .after(Date().addingTimeInterval(3600)))let activity = try Activity.request(
attributes: attributes,
content: initialContent,
pushType: .token // Request push token
)
// Monitor for push token
for await pushToken in activity.pushTokenUpdates {
let tokenString = pushToken.map { String(format: "%02x", $0) }.joined()
// Send to your server
await sendTokenToServer(tokenString, activityID: activity.id)
}
Standard limit is ~10-12 pushes/hour. For live events (sports, stocks), add the com.apple.developer.activity-push-notification-frequent-updates entitlement for significantly higher limits.
Live Activities appear in the Dynamic Island with three size classes:
Shown when another Live Activity is expanded or when multiple activities are active.
DynamicIsland {
DynamicIslandExpandedRegion(.leading) {
Image(systemName: "timer")
}
DynamicIslandExpandedRegion(.trailing) {
Text("\(entry.timeRemaining)")
}
// ...
} compactLeading: {
Image(systemName: "timer")
} compactTrailing: {
Text("\(entry.timeRemaining)")
.frame(width: 40)
}
Shown when more than two Live Activities are active (circular avatar).
DynamicIsland {
// ...
} minimal: {
Image(systemName: "timer")
.foregroundStyle(.tint)
}
Shown when user long-presses the compact view.
DynamicIsland {
DynamicIslandExpandedRegion(.leading) {
Image(systemName: "timer")
.font(.title)
}
DynamicIslandExpandedRegion(.trailing) {
VStack(alignment: .trailing) {
Text("\(entry.timeRemaining)")
.font(.title2.monospacedDigit())
Text("remaining")
.font(.caption)
}
}
DynamicIslandExpandedRegion(.center) {
// Optional center content
}
DynamicIslandExpandedRegion(.bottom) {
HStack {
Button(intent: PauseIntent()) {
Label("Pause", systemImage: "pause.fill")
}
Button(intent: StopIntent()) {
Label("Stop", systemImage: "stop.fill")
}
}
}
}
Content should nest concentrically inside the Dynamic Island's rounded shape with even margins. Use Circle() or RoundedRectangle(cornerRadius:) — never sharp Rectangle() which pokes into corners.
Dynamic Island animations should feel organic and elastic. Use .spring(response: 0.6, dampingFraction: 0.7) or .interpolatingSpring(stiffness: 300, damping: 25) instead of linear animations.
Controls appear in Control Center, Lock Screen, and Action Button (iPhone 15 Pro+).
For simple controls without configuration.
import WidgetKit
import AppIntents
struct TorchControl: ControlWidget {
var body: some ControlWidgetConfiguration {
StaticControlConfiguration(kind: "TorchControl") {
ControlWidgetButton(action: ToggleTorchIntent()) {
Label("Flashlight", systemImage: "flashlight.on.fill")
}
}
.displayName("Flashlight")
.description("Toggle flashlight")
}
}
For configurable controls.
struct TimerControl: ControlWidget {
var body: some ControlWidgetConfiguration {
AppIntentControlConfiguration(
kind: "TimerControl",
intent: ConfigureTimerIntent.self
) { configuration in
ControlWidgetButton(action: StartTimerIntent(duration: configuration.duration)) {
Label("\(configuration.duration)m Timer", systemImage: "timer")
}
}
}
}
For discrete actions (one-shot operations).
ControlWidgetButton(action: PlayMusicIntent()) {
Label("Play", systemImage: "play.fill")
}
.tint(.purple)
For boolean state.
struct AirplaneModeControl: ControlWidget {
var body: some ControlWidgetConfiguration {
StaticControlConfiguration(kind: "AirplaneModeControl") {
ControlWidgetToggle(
isOn: AirplaneModeIntent.isEnabled,
action: AirplaneModeIntent()
) { isOn in
Label(isOn ? "On" : "Off", systemImage: "airplane")
}
}
}
}
For controls needing async state, pass a ControlValueProvider to StaticControlConfiguration:
struct ThermostatProvider: ControlValueProvider {
func currentValue() async throws -> ThermostatValue {
let temp = try await HomeManager.shared.currentTemperature()
return ThermostatValue(temperature: temp)
}
var previewValue: ThermostatValue { ThermostatValue(temperature: 72) }
}
The provider value is passed to your control's closure: { value in ControlWidgetButton(...) }.
Use AppIntentControlConfiguration with a WidgetConfigurationIntent (same pattern as configurable widgets). Add .promptsForUserConfiguration() to show configuration UI when the user adds the control.
.controlWidgetActionHint("Toggles flashlight") — VoiceOver accessibility hint.displayName("My Control") / .description("...") — Shown in Control Center UIWidget rendering modes span multiple iOS versions: widgetAccentable() (iOS 16+), WidgetAccentedRenderingMode (iOS 18+), and Liquid Glass effects like glassEffect() and GlassEffectContainer (iOS 26+). Detect the mode and adapt layout accordingly.
struct MyWidgetView: View {
@Environment(\.widgetRenderingMode) var renderingMode
var body: some View {
if renderingMode == .accented {
// Simplified layout — opaque images tinted white, background replaced with glass
} else {
// Standard full-color layout
}
}
}
Marks views as part of the accent group. In accented mode, accent-group views are tinted separately from primary-group views, creating visual hierarchy.
HStack {
VStack(alignment: .leading) {
Text("Title")
.font(.headline)
.widgetAccentable() // Accent group — tinted in accented mode
Text("Subtitle")
// Primary group by default
}
Image(systemName: "star.fill")
.widgetAccentable() // Also accent group
}
Controls how images render in accented mode. Apply to Image views:
Image("myPhoto")
.widgetAccentedRenderingMode(.accented) // Tinted with accent color
Image("myIcon")
.widgetAccentedRenderingMode(.monochrome) // Rendered as monochrome
Image("myBadge")
.widgetAccentedRenderingMode(.fullColor) // Keeps original colors (opt-out)
Best practices: Display full-color images only in .fullColor rendering mode. Use .widgetAccentable() strategically for visual hierarchy. Test with multiple accent colors and background images.
VStack { /* content */ }
.containerBackground(for: .widget) {
Color.blue.opacity(0.2)
}
In accented mode, the system removes the background and replaces it with themed glass. To prevent removal (excludes widget from iPad Lock Screen, StandBy):
.containerBackgroundRemovable(false)
Text("Label")
.padding()
.glassEffect() // Default capsule shape
Image(systemName: "star.fill")
.frame(width: 60, height: 60)
.glassEffect(.regular, in: .rect(cornerRadius: 12))
Button("Action") { }
.buttonStyle(.glass)
Combine multiple glass elements with GlassEffectContainer:
GlassEffectContainer(spacing: 20.0) {
HStack(spacing: 20.0) {
Image(systemName: "cloud")
.frame(width: 60, height: 60)
.glassEffect()
Image(systemName: "sun")
.frame(width: 60, height: 60)
.glassEffect()
}
}
visionOS widgets are 3D objects placed in physical space — mounted on surfaces or floating. They support unique spatial features.
Widgets can be elevated (on top of surfaces) or recessed (embedded into vertical surfaces like walls):
.supportedMountingStyles([.elevated, .recessed]) // Default is both
// .supportedMountingStyles([.recessed]) // Wall-only widget
If limited to .recessed, users cannot place the widget on horizontal surfaces.
Two visual textures for spatial appearance:
.widgetTexture(.glass) // Default — transparent glass-like appearance
.widgetTexture(.paper) // Poster-like look, effective with extra-large sizes
Widgets adapt to user distance automatically. The system animates transitions between detail levels:
@Environment(\.levelOfDetail) var levelOfDetail
var body: some View {
VStack {
Text(entry.value)
.font(levelOfDetail == .simplified ? .largeTitle : .title)
}
}
Values: .default (close viewing) and .simplified (distance viewing — use larger text, fewer details).
visionOS supports all system families plus extra-large sizes:
.supportedFamilies([
.systemSmall, .systemMedium, .systemLarge,
.systemExtraLarge,
.systemExtraLargePortrait // visionOS-specific portrait orientation
])
Extra-large families are particularly effective with .widgetTexture(.paper) for poster-like displays.
Detect whether the widget background is visible (removed in accented mode):
@Environment(\.showsWidgetContainerBackground) var showsBackground
Add .supplementalActivityFamilies([.medium]) to ActivityConfiguration. Uses StandBy-style full-width dashboard presentation.
Live Activities from paired iPhone appear automatically in macOS Sequoia+ menu bar. No code changes required.
ControlWidget works identically on watchOS — available in Control Center, Action Button, and Smart Stack. Same StaticControlConfiguration / ControlWidgetButton pattern as iOS.
Use .relevanceConfiguration(for:score:attributes:) to help the system promote widgets in Smart Stack. Attributes include .location(CLLocation), .timeOfDay(DateInterval), and .activity(String) for context-aware ranking.
Implement PKPushRegistryDelegate and handle .widgetKit push type to receive server-to-widget pushes. Update shared container data and call WidgetCenter.shared.reloadAllTimelines(). Pushes to iPhone automatically sync to Apple Watch and CarPlay.
Required for sharing data between your app and extensions.
group.com.company.appnamelet sharedContainer = FileManager.default.containerURL(
forSecurityApplicationGroupIdentifier: "group.com.mycompany.myapp"
)!
let dataFileURL = sharedContainer.appendingPathComponent("widgetData.json")
// Main app - write data
let shared = UserDefaults(suiteName: "group.com.mycompany.myapp")!
shared.set("Updated value", forKey: "myKey")
// Widget extension - read data
let shared = UserDefaults(suiteName: "group.com.mycompany.myapp")!
let value = shared.string(forKey: "myKey")
Point NSPersistentStoreDescription at the shared container URL:
let sharedStoreURL = FileManager.default.containerURL(
forSecurityApplicationGroupIdentifier: "group.com.mycompany.myapp"
)!.appendingPathComponent("MyApp.sqlite")
let description = NSPersistentStoreDescription(url: sharedStoreURL)
container.persistentStoreDescriptions = [description]
config.sharedContainerIdentifier to your App Group ID for downloads accessible by extensionsCFNotificationCenterPostNotification / CFNotificationCenterAddObserver with CFNotificationCenterGetDarwinNotifyCenter() for simple cross-process signals (e.g., notify widget to call WidgetCenter.shared.reloadAllTimelines())Add .supplementalActivityFamilies([.small]) to ActivityConfiguration to show Live Activities on Apple Watch Smart Stack (same modifier used for CarPlay with .medium).
Use @Environment(\.activityFamily) to adapt layout — check for .small (watchOS) vs iPhone layout.
Use @Environment(\.isLuminanceReduced) to simplify views for Always On Display — reduce detail, use white text, larger fonts. Combine with @Environment(\.colorScheme) for proper dark mode handling.
watchOS updates sync automatically with iPhone via push notifications. Updates may be delayed if watch is out of Bluetooth range.
For a complete step-by-step tutorial with working code examples, see Apple's Building Widgets Using WidgetKit and SwiftUI sample project.
Key steps: Add widget extension target, configure App Groups, implement TimelineProvider, design SwiftUI view, update from main app. See Expert Review Checklist below for production requirements.
Architecture:
UserDefaults.standard in widget codePerformance:
Data & State:
User Experience:
Live Activities (if applicable):
Liquid Glass (if applicable):
widgetAccentable() applied for visual hierarchy in accented modeWidgetAccentedRenderingMode set on images (.accented, .monochrome, or .fullColor).containerBackground(for: .widget)visionOS (if applicable):
.elevated, .recessed, or both).glass or .paper)levelOfDetail handled for proximity-aware layouts.systemExtraLarge, .systemExtraLargePortrait)Control Center Widgets (if applicable):
Testing:
Test placeholder(), getSnapshot(), and getTimeline() methods. Save test data to shared container, call getTimeline() with a mock context, assert entries are non-empty and contain expected data. Use waitForExpectations(timeout: 5.0) for async timeline generation.
print() logging in getTimeline() to verify it's being called and data is loadedFileManager.default.containerURL(forSecurityApplicationGroupIdentifier:) in both app and widget — paths must matchWidgetCenter.shared.reloadAllTimelines()Widget not appearing in gallery: Check WidgetBundle includes it, verify supportedFamilies(), check extension's "Skip Install" = NO, verify deployment target matches app.
Symptoms: Widget shows stale data, doesn't update
Diagnostic Steps:
.atEnd vs .after() vs .never)getTimeline() is being called (add logging)Solution:
// Manual reload from main app when data changes
import WidgetKit
WidgetCenter.shared.reloadAllTimelines()
// or
WidgetCenter.shared.reloadTimelines(ofKind: "MyWidget")
Symptoms: Widget shows default/empty data
Diagnostic Steps:
Solution:
// Both app AND extension must use:
let shared = UserDefaults(suiteName: "group.com.mycompany.myapp")!
// NOT:
let shared = UserDefaults.standard // ❌ Different containers
Symptoms: Activity.request() throws error
Common Errors:
"Activity size exceeds 4KB":
// ❌ BAD: Large images in attributes
struct MyAttributes: ActivityAttributes {
var productImage: UIImage // Too large!
}
// ✅ GOOD: Use asset catalog names
struct MyAttributes: ActivityAttributes {
var productImageName: String // Reference to asset
}
"Activities not enabled":
// Check authorization first
let authInfo = ActivityAuthorizationInfo()
guard authInfo.areActivitiesEnabled else {
throw ActivityError.notEnabled
}
Symptoms: Tapping button does nothing
Diagnostic Steps:
perform() returns IntentResultintent: parameter, not action:Solution:
// ✅ CORRECT: Use intent parameter
Button(intent: MyIntent()) {
Label("Action", systemImage: "star")
}
// ❌ WRONG: Don't use action closure
Button(action: { /* This won't work in widgets */ }) {
Label("Action", systemImage: "star")
}
Control Center widget slow: Use async in ControlValueProvider.currentValue(), never block with Thread.sleep. Provide fast previewValue fallback.
Widget shows wrong size: Switch on @Environment(\.widgetFamily) in view, adapt layout per family, avoid hardcoded sizes.
Timeline entries out of order: Ensure entry dates are chronological. Use incrementing offsets from Date().
watchOS Live Activity not showing: Add .supplementalActivityFamilies([.small]) to ActivityConfiguration, verify watchOS 11+, check Bluetooth/pairing.
Symptoms: Widget rendering slow, battery drain
Common Causes:
getTimeline()Solution:
// ✅ GOOD: Strategic intervals
let entries = (0..<8).map { offset in
let date = Calendar.current.date(byAdding: .minute, value: offset * 15, to: now)!
return SimpleEntry(date: date, data: precomputedData)
}
// ❌ BAD: Too frequent, too many entries
let entries = (0..<100).map { offset in
let date = Calendar.current.date(byAdding: .minute, value: offset, to: now)!
return SimpleEntry(date: date, data: fetchFromNetwork()) // Network in timeline
}
getTimeline(). Test timeline logic with unit tests or device runs.print() in getTimeline() — verify it's called and data loadsWidgetCenter.shared.getCurrentConfigurations() to verify registrationSwiftData in Widgets (iOS 17+):
ModelContainer in widget with same schema as main appModelConfiguration(url: containerURL)WidgetCenter.shared.reloadAllTimelines() after writesGRDB/SQLite in Widgets:
DatabasePool (not DatabaseQueue) for concurrent readstry DatabasePool(path: dbPath, configuration: readOnlyConfig)configuration.readonly = true in widget to prevent accidental writesWWDC: 2025-278, 2024-10157, 2024-10068, 2024-10098, 2023-10028, 2023-10194, 2022-10184, 2022-10185
Docs: /widgetkit, /activitykit, /appintents
Skills: axiom-app-intents-ref, axiom-swift-concurrency, axiom-swiftui-performance, axiom-swiftui-layout, axiom-extensions-widgets
Version: 0.9 | Platforms: iOS 14+, iPadOS 14+, watchOS 9+, macOS 11+, visionOS 2+
Activates when the user asks about AI prompts, needs prompt templates, wants to search for prompts, or mentions prompts.chat. Use for discovering, retrieving, and improving prompts.
Search, retrieve, and install Agent Skills from the prompts.chat registry using MCP tools. Use when the user asks to find skills, browse skill catalogs, install a skill for Claude, or extend Claude's capabilities with reusable AI agent components.
Creating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems. Create original algorithmic art rather than copying existing artists' work to avoid copyright violations.