Community-maintained Agent Skills for complete Swift and Apple-platform app delivery.
73
92%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Medium
Suggest reviewing before use
Read this reference only when the task matches the sections below.
Pre-compute display values in the timeline provider. Pass display-ready data through the entry.
// WRONG: Heavy computation in the widget view
struct MyWidgetView: View {
let entry: RawDataEntry
var body: some View {
let processed = HeavyProcessor.process(entry.rawData) // Slow
Text(processed.summary)
}
}
// CORRECT: Pre-compute in the provider
func timeline(for configuration: Intent, in context: Context) async -> Timeline<ProcessedEntry> {
let raw = await DataStore.shared.fetch()
let processed = HeavyProcessor.process(raw)
let entry = ProcessedEntry(date: .now, summary: processed.summary, value: processed.value)
return Timeline(entries: [entry], policy: .atEnd)
}Widget extensions run with strict memory limits. Avoid:
// WRONG: Loading a full-resolution image
Image(uiImage: UIImage(contentsOfFile: fullResPath)!)
// CORRECT: Use a pre-resized thumbnail stored in the shared container
Image(uiImage: UIImage(contentsOfFile: thumbnailPath)!)
.resizable()
.aspectRatio(contentMode: .fill)// In the main app: write data
let defaults = UserDefaults(suiteName: "group.com.example.myapp")
defaults?.set(encodedData, forKey: "widgetData")
WidgetCenter.shared.reloadTimelines(ofKind: "MyWidget")
// In the widget provider: read data
func timeline(for configuration: Intent, in context: Context) async -> Timeline<MyEntry> {
let defaults = UserDefaults(suiteName: "group.com.example.myapp")
let data = defaults?.data(forKey: "widgetData")
// Decode and build entry
}For larger datasets, use a shared SQLite database or Core Data store in the App Group container.
| Entitlement | Purpose |
|---|---|
App Groups (com.apple.security.application-groups) | Share data between app and widget |
Push Notifications (aps-environment) | Required for push-based Live Activity updates |
group.com.example.myapp).UserDefaults(suiteName:) or FileManager.containerURL(forSecurityApplicationGroupIdentifier:)
for shared storage.// ERROR: "Widget extension must include at least one widget"
// FIX: Ensure @main is on the WidgetBundle, not a widget struct.
// ERROR: "No such module 'WidgetKit'"
// FIX: Ensure the widget extension target links WidgetKit and SwiftUI frameworks.
// ERROR: "The operation couldn't be completed. (ActivityKit.ActivityAuthorizationError error 3.)"
// FIX: Add NSSupportsLiveActivities = YES to the HOST APP's Info.plist (not the extension).Score entries to surface widgets in Smart Stacks when relevant:
struct GameEntry: TimelineEntry {
var date: Date
var score: String
var isLive: Bool
var relevance: TimelineEntryRelevance? {
isLive ? TimelineEntryRelevance(score: 100, duration: 3600) : nil
}
}Higher scores make the widget more likely to surface. The duration specifies
how long the relevance lasts.
On iPhone and iPad, prefer TimelineEntryRelevance on timeline entries and
donate App Intents that match configurable widget parameters. Smart Stacks on
iPhone and iPad don't use the timeline provider's relevance() callback.
On watchOS, use relevance() only when providing RelevanceKit contextual clues.
Return WidgetRelevance([WidgetRelevanceAttribute(...)]); there is no
WidgetRelevance(intent, score:) initializer.
Track the full lifecycle of a Live Activity:
Task {
for await state in activity.activityStateUpdates {
switch state {
case .active:
// Activity is running and visible
break
case .pending:
// Requested but not yet displayed (iOS 26+)
break
case .stale:
// Content is outdated; update or end
break
case .ended:
// Ended but may still be visible on Lock Screen
break
case .dismissed:
// Fully removed from UI; clean up resources
break
@unknown default:
break
}
}
}Control Live Activity persistence behavior (iOS 18+):
// Standard: persists until explicitly ended
let activity = try Activity.request(
attributes: attributes,
content: content,
pushType: .token,
style: .standard
)
// Transient: appears in Dynamic Island's extended presentation and ends
// automatically when the user leaves that interaction context.
let activity = try Activity.request(
attributes: attributes,
content: content,
pushType: .token,
style: .transient
)Use .transient for short interactions that should not persist as a standard
Live Activity after the user locks the device, collapses the Dynamic Island,
leaves the app, or performs other tasks outside the Dynamic Island.
Control when an ended Live Activity disappears from the Lock Screen:
// System-determined timing (default)
await activity.end(finalContent, dismissalPolicy: .default)
// Remove immediately
await activity.end(finalContent, dismissalPolicy: .immediate)
// Remove after a specific date (max 4 hours)
let removalDate = Date().addingTimeInterval(3600)
await activity.end(finalContent, dismissalPolicy: .after(removalDate))let widgets = try await WidgetCenter.shared.currentConfigurations()
for widget in widgets {
print("Kind: \(widget.kind), Family: \(widget.family)")
}let activities = Activity<DeliveryAttributes>.activities
for activity in activities {
print("ID: \(activity.id), State: \(activity.activityState)")
}Task {
for await activity in Activity<DeliveryAttributes>.activityUpdates {
print("New activity started: \(activity.id)")
}
}Use Gauge (iOS 16+) instead of manual Circle or Path arcs to show a value
within a range. The system handles styling, accessibility, and rendering-mode
adaptation automatically.
.accessoryCircular — open ring with center value label, matches the system
complication style. Use for accessoryCircular Lock Screen widgets..linearCapacity — horizontal bar that fills leading to trailing. Use for
home screen widgets when a capacity bar fits.// accessoryCircular Lock Screen widget
struct StepsCircularView: View {
let entry: StepsEntry
var body: some View {
Gauge(value: Double(entry.stepCount), in: 0...10000) {
Image(systemName: "figure.walk")
} currentValueLabel: {
Text("\(entry.stepCount)")
}
.gaugeStyle(.accessoryCircular)
}
}
// Home screen capacity bar
Gauge(value: storageUsed, in: 0...storageTotal) {
Text("Storage")
} currentValueLabel: {
Text(storageUsed, format: .byteCount(style: .file))
}
.gaugeStyle(.linearCapacity).containerBackground(_:for: .widget) (iOS 17+) is the designated way to set
widget backgrounds. Replaces older padding and background patterns. The system
uses this placement to correctly render backgrounds across all widget surfaces.
struct OrderWidgetView: View {
let entry: OrderEntry
var body: some View {
VStack(alignment: .leading) {
Text(entry.orderName).font(.headline)
Text(entry.status).foregroundStyle(.secondary)
}
.containerBackground(.fill.tertiary, for: .widget)
}
}Use Canvas for sparklines, mini bar charts, or heat maps inside widgets. The
lack of per-element accessibility is acceptable since the entire widget surface
is a single tap target.
struct SparklineView: View {
let values: [Double]
var body: some View {
Canvas { context, size in
guard values.count > 1 else { return }
let maxVal = values.max() ?? 1
let step = size.width / CGFloat(values.count - 1)
var path = Path()
for (i, value) in values.enumerated() {
let x = step * CGFloat(i)
let y = size.height * (1 - value / maxVal)
if i == 0 { path.move(to: CGPoint(x: x, y: y)) }
else { path.addLine(to: CGPoint(x: x, y: y)) }
}
context.stroke(path, with: .color(.blue), lineWidth: 2)
}
}
}Apple budgets 40–70 refreshes per day for frequently viewed widgets, with entries at least 5 minutes apart. Align reload cadence to how often the underlying data actually changes.
.after(date) when data updates on a known schedule (market hours, transit)..never when data only changes from user action.Text(timerInterval:countsDown:) for live countdowns instead of burning
timeline entries on every tick..tessl-plugin
skills
accessorysetupkit
references
activitykit
adattributionkit
references
alarmkit
references
app-clips
app-intents
app-store-optimization
app-store-review
apple-on-device-ai
appmigrationkit
audioaccessorykit
references
authentication
references
avkit
background-processing
references
browserenginekit
callkit
references
carplay
cloudkit
contacts-framework
references
core-bluetooth
references
core-data
core-motion
references
core-nfc
references
coreml
references
cryptokit
cryptotokenkit
references
debugging-instruments
device-integrity
references
dockkit
energykit
references
eventkit
financekit
references
focus-engine
gamekit
healthkit
references
homekit
references
ios-accessibility
ios-app-workflow
references
ios-ettrace-performance
ios-localization
ios-memgraph-analysis
ios-networking
ios-simulator
references
metrickit
references
musickit
references
natural-language
references
paperkit
references
passkit
references
pdfkit
pencilkit
references
permissionkit
references
photokit
push-notifications
realitykit
references
relevancekit
references
scenekit
sensorkit
speech-recognition
references
spritekit
storekit
swift-api-design-guidelines
swift-architecture
references
swift-charts
swift-codable
references
swift-code-review
swift-concurrency
swift-formatstyle
references
swift-language
swift-security
references
swift-testing
swiftdata
swiftlint
swiftui-animation
swiftui-gestures
references
swiftui-layout-components
swiftui-liquid-glass
references
swiftui-patterns
swiftui-performance
swiftui-responsive-layout
swiftui-uikit-interop
swiftui-webkit
tabletopkit
tipkit
vision-framework
weatherkit
references