ios-widgets/SKILL.md
Create iOS widgets, App Intents, Live Activities, and app extensions. Use when building widgets, creating control center controls, implementing interactive widgets, or adding share/action extensions. Triggers on widget, WidgetKit, App Intent, Live Activity, extension, control center, interactive widget, timeline, widget configuration.
npx skillsauth add abanoub-ashraf/manus-skills-import ios-widgetsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
You are a widgets and extensions specialist for iOS. When this skill activates, help create powerful home screen experiences.
import WidgetKit
import SwiftUI
struct MyWidget: Widget {
let kind: String = "MyWidget"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider()) { entry in
MyWidgetEntryView(entry: entry)
.containerBackground(.fill.tertiary, for: .widget)
}
.configurationDisplayName("My Widget")
.description("Shows important information at a glance.")
.supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
}
}
// Timeline Entry
struct SimpleEntry: TimelineEntry {
let date: Date
let data: WidgetData
}
// Timeline Provider
struct Provider: TimelineProvider {
func placeholder(in context: Context) -> SimpleEntry {
SimpleEntry(date: Date(), data: .placeholder)
}
func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> Void) {
let entry = SimpleEntry(date: Date(), data: .placeholder)
completion(entry)
}
func getTimeline(in context: Context, completion: @escaping (Timeline<SimpleEntry>) -> Void) {
Task {
let data = await fetchWidgetData()
let entry = SimpleEntry(date: Date(), data: data)
// Refresh every 15 minutes
let nextUpdate = Calendar.current.date(byAdding: .minute, value: 15, to: Date())!
let timeline = Timeline(entries: [entry], policy: .after(nextUpdate))
completion(timeline)
}
}
}
// Widget View
struct MyWidgetEntryView: View {
var entry: Provider.Entry
@Environment(\.widgetFamily) var family
var body: some View {
switch family {
case .systemSmall:
SmallWidgetView(data: entry.data)
case .systemMedium:
MediumWidgetView(data: entry.data)
case .systemLarge:
LargeWidgetView(data: entry.data)
default:
Text("Unsupported")
}
}
}
import AppIntents
import WidgetKit
// Configuration Intent
struct SelectCategoryIntent: WidgetConfigurationIntent {
static var title: LocalizedStringResource = "Select Category"
static var description: IntentDescription = "Choose which category to display"
@Parameter(title: "Category")
var category: CategoryEntity?
}
// Entity for the parameter
struct CategoryEntity: AppEntity {
let id: String
let name: String
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Category"
static var defaultQuery = CategoryQuery()
var displayRepresentation: DisplayRepresentation {
DisplayRepresentation(title: "\(name)")
}
}
// Query to provide options
struct CategoryQuery: EntityQuery {
func entities(for identifiers: [String]) async throws -> [CategoryEntity] {
// Fetch categories by IDs
return identifiers.compactMap { id in
CategoryEntity(id: id, name: "Category \(id)")
}
}
func suggestedEntities() async throws -> [CategoryEntity] {
// Return available options
return [
CategoryEntity(id: "1", name: "Work"),
CategoryEntity(id: "2", name: "Personal"),
CategoryEntity(id: "3", name: "Health")
]
}
func defaultResult() async -> CategoryEntity? {
try? await suggestedEntities().first
}
}
// Configurable Widget
struct ConfigurableWidget: Widget {
var body: some WidgetConfiguration {
AppIntentConfiguration(
kind: "ConfigurableWidget",
intent: SelectCategoryIntent.self,
provider: ConfigurableProvider()
) { entry in
ConfigurableWidgetView(entry: entry)
.containerBackground(.fill.tertiary, for: .widget)
}
.configurationDisplayName("Category Widget")
.description("Display tasks from a specific category")
.supportedFamilies([.systemSmall, .systemMedium])
}
}
struct ConfigurableProvider: AppIntentTimelineProvider {
func placeholder(in context: Context) -> ConfigurableEntry {
ConfigurableEntry(date: Date(), category: nil, items: [])
}
func snapshot(for configuration: SelectCategoryIntent, in context: Context) async -> ConfigurableEntry {
ConfigurableEntry(date: Date(), category: configuration.category, items: [])
}
func timeline(for configuration: SelectCategoryIntent, in context: Context) async -> Timeline<ConfigurableEntry> {
let items = await fetchItems(for: configuration.category?.id)
let entry = ConfigurableEntry(date: Date(), category: configuration.category, items: items)
return Timeline(entries: [entry], policy: .after(Date().addingTimeInterval(3600)))
}
}
import AppIntents
// App Intent for widget button
struct ToggleTaskIntent: AppIntent {
static var title: LocalizedStringResource = "Toggle Task"
@Parameter(title: "Task ID")
var taskID: String
init() { }
init(taskID: String) {
self.taskID = taskID
}
func perform() async throws -> some IntentResult {
// Toggle the task in your data store
await TaskStore.shared.toggleTask(id: taskID)
// Reload widget timeline
WidgetCenter.shared.reloadTimelines(ofKind: "TaskWidget")
return .result()
}
}
// Interactive Widget View
struct TaskWidgetView: View {
let tasks: [TaskItem]
var body: some View {
VStack(alignment: .leading, spacing: 8) {
ForEach(tasks) { task in
HStack {
// Interactive button
Button(intent: ToggleTaskIntent(taskID: task.id)) {
Image(systemName: task.isCompleted ? "checkmark.circle.fill" : "circle")
}
.buttonStyle(.plain)
Text(task.title)
.strikethrough(task.isCompleted)
}
}
}
}
}
import ActivityKit
import WidgetKit
// Activity Attributes
struct DeliveryActivityAttributes: ActivityAttributes {
public struct ContentState: Codable, Hashable {
var status: String
var estimatedArrival: Date
var driverName: String
}
var orderNumber: String
var restaurantName: String
}
// Live Activity Widget
struct DeliveryLiveActivity: Widget {
var body: some WidgetConfiguration {
ActivityConfiguration(for: DeliveryActivityAttributes.self) { context in
// Lock Screen / Banner
DeliveryLockScreenView(context: context)
} dynamicIsland: { context in
DynamicIsland {
// Expanded
DynamicIslandExpandedRegion(.leading) {
Image(systemName: "bicycle")
}
DynamicIslandExpandedRegion(.trailing) {
Text(context.state.estimatedArrival, style: .timer)
}
DynamicIslandExpandedRegion(.bottom) {
Text(context.state.status)
}
} compactLeading: {
Image(systemName: "bicycle")
} compactTrailing: {
Text(context.state.estimatedArrival, style: .timer)
} minimal: {
Image(systemName: "bicycle")
}
}
}
}
// Starting a Live Activity
class DeliveryManager {
var activity: Activity<DeliveryActivityAttributes>?
func startTracking(orderNumber: String, restaurant: String) async throws {
guard ActivityAuthorizationInfo().areActivitiesEnabled else {
throw DeliveryError.activitiesNotEnabled
}
let attributes = DeliveryActivityAttributes(
orderNumber: orderNumber,
restaurantName: restaurant
)
let initialState = DeliveryActivityAttributes.ContentState(
status: "Preparing your order",
estimatedArrival: Date().addingTimeInterval(1800),
driverName: ""
)
activity = try Activity.request(
attributes: attributes,
content: .init(state: initialState, staleDate: nil),
pushType: .token // For push updates
)
}
func updateStatus(_ status: String, arrival: Date, driver: String) async {
let updatedState = DeliveryActivityAttributes.ContentState(
status: status,
estimatedArrival: arrival,
driverName: driver
)
await activity?.update(
ActivityContent(state: updatedState, staleDate: nil)
)
}
func endActivity() async {
let finalState = DeliveryActivityAttributes.ContentState(
status: "Delivered!",
estimatedArrival: Date(),
driverName: ""
)
await activity?.end(
ActivityContent(state: finalState, staleDate: nil),
dismissalPolicy: .after(.now + 300) // Dismiss after 5 min
)
}
}
import WidgetKit
import SwiftUI
struct ToggleLightControl: ControlWidget {
var body: some ControlWidgetConfiguration {
StaticControlConfiguration(
kind: "com.app.toggleLight"
) {
ControlWidgetToggle(
"Living Room",
isOn: LightManager.shared.isLivingRoomOn,
action: ToggleLightIntent()
) { isOn in
Label(isOn ? "On" : "Off", systemImage: isOn ? "lightbulb.fill" : "lightbulb")
}
}
.displayName("Light Control")
.description("Toggle your lights")
}
}
struct ToggleLightIntent: SetValueIntent {
static var title: LocalizedStringResource = "Toggle Light"
@Parameter(title: "Enabled")
var value: Bool
func perform() async throws -> some IntentResult {
await LightManager.shared.setLivingRoom(on: value)
return .result()
}
}
// 1. Enable App Groups in both app and widget targets
// 2. Use shared container
let sharedDefaults = UserDefaults(suiteName: "group.com.app.shared")
// Save from app
sharedDefaults?.set(data, forKey: "widgetData")
// Read in widget
let data = sharedDefaults?.data(forKey: "widgetData")
// Shared Core Data / SwiftData
let containerURL = FileManager.default
.containerURL(forSecurityApplicationGroupIdentifier: "group.com.app.shared")!
.appendingPathComponent("Model.sqlite")
import WidgetKit
// Reload specific widget
WidgetCenter.shared.reloadTimelines(ofKind: "MyWidget")
// Reload all widgets
WidgetCenter.shared.reloadAllTimelines()
// Get current configurations
WidgetCenter.shared.getCurrentConfigurations { result in
switch result {
case .success(let widgets):
for widget in widgets {
print("Widget: \(widget.kind), Family: \(widget.family)")
}
case .failure(let error):
print("Error: \(error)")
}
}
// In widget view
struct MyWidgetView: View {
var body: some View {
Link(destination: URL(string: "myapp://task/123")!) {
TaskRow()
}
// Or for the entire widget
VStack {
content
}
.widgetURL(URL(string: "myapp://widget/main"))
}
}
// In main app
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.onOpenURL { url in
handleWidgetURL(url)
}
}
}
func handleWidgetURL(_ url: URL) {
guard url.scheme == "myapp" else { return }
switch url.host {
case "task":
let taskID = url.pathComponents.last
// Navigate to task
case "widget":
// Handle widget tap
default:
break
}
}
}
import SwiftUI
struct ShareView: View {
let extensionContext: NSExtensionContext?
@State private var sharedText = ""
var body: some View {
NavigationStack {
Form {
TextField("Add note", text: $sharedText)
}
.navigationTitle("Save to App")
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Cancel") {
extensionContext?.cancelRequest(withError: NSError(domain: "", code: 0))
}
}
ToolbarItem(placement: .confirmationAction) {
Button("Save") {
saveAndClose()
}
}
}
}
.task {
await loadSharedContent()
}
}
func loadSharedContent() async {
guard let item = extensionContext?.inputItems.first as? NSExtensionItem,
let provider = item.attachments?.first else { return }
if provider.hasItemConformingToTypeIdentifier("public.url") {
if let url = try? await provider.loadItem(forTypeIdentifier: "public.url") as? URL {
sharedText = url.absoluteString
}
}
}
func saveAndClose() {
// Save via App Group
let defaults = UserDefaults(suiteName: "group.com.app.shared")
var items = defaults?.stringArray(forKey: "sharedItems") ?? []
items.append(sharedText)
defaults?.set(items, forKey: "sharedItems")
extensionContext?.completeRequest(returningItems: nil)
}
}
development
Design principles for building polished, native-feeling SwiftUI apps and widgets. Use this skill when creating or modifying SwiftUI views, iOS widgets (WidgetKit), or any native Apple UI. Ensures proper spacing, typography, colors, and widget implementations that look and feel like quality apps rather than AI-generated slop.
data-ai
Design and implement SwiftUI views, components, and app architecture. Use when creating new SwiftUI views, implementing MVVM/TCA patterns, managing state with @Observable, @State, @Binding, or @Environment, designing navigation flows, or structuring iOS app architecture. Triggers on SwiftUI, view model, state management, navigation, coordinator pattern.
development
Implement, review, or improve SwiftUI animations and transitions. Use when adding implicit or explicit animations with withAnimation, configuring spring animations (.smooth, .snappy, .bouncy), building phase or keyframe animations with PhaseAnimator/KeyframeAnimator, creating hero transitions with matchedGeometryEffect or matchedTransitionSource, adding SF Symbol effects (bounce, pulse, variableColor, breathe, rotate, wiggle), implementing custom Transition or CustomAnimation types, or ensuring animations respect accessibilityReduceMotion.
testing
Audit SwiftUI views for accessibility (iOS + macOS) with patch-ready fixes