diff --git a/examples/ios-widget-steps/README.md b/examples/ios-widget-steps/README.md
new file mode 100644
index 0000000000..ab6def3cf3
--- /dev/null
+++ b/examples/ios-widget-steps/README.md
@@ -0,0 +1,123 @@
+# iOS 26 – Schritte Widget (Apple Health)
+
+Ein vollständiges, importierbares Xcode-Projekt mit einer iOS-App und einem WidgetKit-Extension, das täglich Schritt-Daten direkt aus Apple Health liest.
+
+## Vorschau
+
+| Small | Medium | Lock Screen (Circular) | Lock Screen (Rectangular) |
+|-------|--------|------------------------|---------------------------|
+| Fortschrittsring + Schrittzahl | Ring + Distanz, Ziel, Fortschrittsbalken | Gauge-Ring mit kompakter Zahl | Schrittzahl + Fortschrittsbalken |
+
+## Funktionen
+
+- **Echtzeitdaten** aus Apple Health (HealthKit)
+- **Fortschrittsring** – animiert, wird grün bei Zielerreichung
+- **Distanzberechnung** (km) und **Kalorienabschätzung** (kcal)
+- **Anpassbares Tagesziel** (Standard: 10.000) – wird im App-Group-UserDefaults gespeichert
+- **Automatische Widget-Aktualisierung** alle 15 Minuten
+- **5 Widget-Größen:** Small, Medium, Accessory Circular, Rectangular, Inline (Lock Screen)
+- Vollständige **Xcode 16 Previews** mit `#Preview(as:)` Makro
+
+## Anforderungen
+
+| | |
+|---|---|
+| iOS | 26.0+ |
+| Xcode | 16.0+ |
+| Swift | 6.0 |
+| Frameworks | HealthKit, WidgetKit, SwiftUI |
+
+## Projekt öffnen
+
+```bash
+open examples/ios-widget-steps/StepsWidget/StepsWidget.xcodeproj
+```
+
+## Projektstruktur
+
+```
+StepsWidget/
+├── StepsWidget.xcodeproj/
+│ └── project.pbxproj
+│
+├── StepsApp/ # Hauptapp-Target
+│ ├── StepsApp.swift # @main App
+│ ├── ContentView.swift # Fortschrittsring, Stats, Ziel-Editor
+│ ├── HealthKitManager.swift # HealthKit-Abfragen + WidgetCenter reload
+│ ├── Info.plist # NSHealthShareUsageDescription
+│ ├── StepsApp.entitlements # HealthKit + App Group
+│ └── Assets.xcassets/
+│
+└── StepsWidgetExtension/ # Widget-Extension-Target
+ ├── StepsWidgetBundle.swift # @main WidgetBundle
+ ├── StepsProvider.swift # TimelineProvider + StepsEntry
+ ├── StepsWidgetView.swift # Alle Widget-Views + Widget-Config
+ ├── Info.plist # NSExtensionPointIdentifier
+ ├── StepsWidgetExtension.entitlements # HealthKit + App Group
+ └── Assets.xcassets/
+```
+
+## Setup nach dem Öffnen in Xcode
+
+### 1. Team & Bundle ID setzen
+
+Wähle in Xcode beide Targets (`StepsApp` und `StepsWidgetExtension`) → **Signing & Capabilities** → wähle dein Team.
+
+Die Bundle-IDs sind vorbelegt als:
+- App: `com.example.stepswidget`
+- Widget: `com.example.stepswidget.widget`
+
+Passe sie auf deine eigene Domain an, z. B. `de.deinname.stepswidget`.
+
+### 2. App Group konfigurieren
+
+Beide Targets müssen zur **gleichen App Group** gehören, damit das Widget das Tagesziel aus der App lesen kann:
+
+1. Target `StepsApp` → **Signing & Capabilities** → **+ Capability** → *App Groups*
+2. Gruppe hinzufügen: `group.com.example.stepswidget`
+ *(oder deine eigene: `group.de.deinname.stepswidget`)*
+3. Dasselbe für `StepsWidgetExtension` wiederholen
+4. Den App-Group-Identifier in `HealthKitManager.swift` und `StepsProvider.swift` anpassen:
+ ```swift
+ private static let appGroup = "group.com.example.stepswidget"
+ ```
+
+### 3. HealthKit-Berechtigung
+
+Die App fragt beim ersten Start automatisch nach HealthKit-Zugriff.
+Das Widget fordert die Berechtigung ebenfalls beim ersten Aktualisieren an.
+
+> **Hinweis:** Der iOS-Simulator enthält **keine** HealthKit-Daten.
+> Zum Testen ein **echtes Gerät** verwenden oder die Simulator-Health-App zum Eintragen von Schritten nutzen.
+
+## Architektur
+
+```
+┌─────────────────────────────────┐ App Group UserDefaults
+│ StepsApp │ ───────────────────────────────┐
+│ HealthKitManager (@MainActor) │ schreibt stepGoal │
+│ ├─ requestAuthorization() │ │
+│ ├─ fetchTodaySteps() async │ ▼
+│ └─ updateGoal(_:) → WidgetCenter.reloadTimelines() ┌──────────────────────┐
+└─────────────────────────────────┘ │ StepsWidgetExtension │
+ │ StepsProvider │
+HealthKit Store │ ├─ liest stepGoal │
+ │ HKStatisticsQuery (stepCount) │ └─ HKStatisticsQuery │
+ └──────────────────────────────────────────────────┤ │
+ │ StepsWidget (Views) │
+ └──────────────────────┘
+```
+
+## Widget-Aktualisierung
+
+Das Widget aktualisiert sich automatisch alle **15 Minuten** (WidgetKit policy).
+Wenn der Nutzer das Tagesziel in der App ändert, wird das Widget sofort neu geladen:
+
+```swift
+WidgetCenter.shared.reloadTimelines(ofKind: "StepsWidget")
+```
+
+## Lokalisierung
+
+Alle Texte sind auf Deutsch. Für andere Sprachen können die Strings in eine
+`Localizable.xcstrings`-Datei extrahiert werden (`SWIFT_EMIT_LOC_STRINGS = YES` ist gesetzt).
diff --git a/examples/ios-widget-steps/StepsWidget/StepsApp/Assets.xcassets/AccentColor.colorset/Contents.json b/examples/ios-widget-steps/StepsWidget/StepsApp/Assets.xcassets/AccentColor.colorset/Contents.json
new file mode 100644
index 0000000000..805e83152a
--- /dev/null
+++ b/examples/ios-widget-steps/StepsWidget/StepsApp/Assets.xcassets/AccentColor.colorset/Contents.json
@@ -0,0 +1,38 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "1.000",
+ "green" : "0.478",
+ "red" : "0.000"
+ }
+ },
+ "idiom" : "universal"
+ },
+ {
+ "appearances" : [
+ {
+ "appearance" : "luminosity",
+ "value" : "dark"
+ }
+ ],
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "1.000",
+ "green" : "0.478",
+ "red" : "0.000"
+ }
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/examples/ios-widget-steps/StepsWidget/StepsApp/Assets.xcassets/AppIcon.appiconset/Contents.json b/examples/ios-widget-steps/StepsWidget/StepsApp/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000000..13613e3ee1
--- /dev/null
+++ b/examples/ios-widget-steps/StepsWidget/StepsApp/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,13 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "platform" : "ios",
+ "size" : "1024x1024"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/examples/ios-widget-steps/StepsWidget/StepsApp/Assets.xcassets/Contents.json b/examples/ios-widget-steps/StepsWidget/StepsApp/Assets.xcassets/Contents.json
new file mode 100644
index 0000000000..73c00596a7
--- /dev/null
+++ b/examples/ios-widget-steps/StepsWidget/StepsApp/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/examples/ios-widget-steps/StepsWidget/StepsApp/ContentView.swift b/examples/ios-widget-steps/StepsWidget/StepsApp/ContentView.swift
new file mode 100644
index 0000000000..a0c817fb3b
--- /dev/null
+++ b/examples/ios-widget-steps/StepsWidget/StepsApp/ContentView.swift
@@ -0,0 +1,182 @@
+import SwiftUI
+
+struct ContentView: View {
+ @StateObject private var manager = HealthKitManager()
+ @State private var showGoalSheet = false
+ @State private var goalDraft: Double = 10_000
+
+ private var ringColor: Color { manager.progress >= 1.0 ? .green : .blue }
+
+ var body: some View {
+ NavigationStack {
+ ScrollView {
+ VStack(spacing: 28) {
+ progressRing
+ statsGrid
+ goalCard
+ if manager.authorizationStatus == .denied { deniedBanner }
+ }
+ .padding(.bottom, 24)
+ }
+ .navigationTitle("Schritte")
+ .navigationBarTitleDisplayMode(.large)
+ .toolbar {
+ ToolbarItem(placement: .topBarTrailing) {
+ Button {
+ Task { await manager.fetchTodaySteps() }
+ } label: {
+ Image(systemName: "arrow.clockwise")
+ }
+ }
+ }
+ .sheet(isPresented: $showGoalSheet) {
+ GoalEditorSheet(goal: $goalDraft) { manager.updateGoal(Int(goalDraft)) }
+ }
+ .task { manager.requestAuthorization() }
+ }
+ }
+
+ // MARK: - Sub-views
+
+ private var progressRing: some View {
+ ZStack {
+ Circle()
+ .stroke(ringColor.opacity(0.15), lineWidth: 22)
+ Circle()
+ .trim(from: 0, to: manager.progress)
+ .stroke(ringColor, style: StrokeStyle(lineWidth: 22, lineCap: .round))
+ .rotationEffect(.degrees(-90))
+ .animation(.easeInOut(duration: 0.8), value: manager.progress)
+ VStack(spacing: 4) {
+ Text(manager.todaySteps.formatted())
+ .font(.system(size: 52, weight: .bold, design: .rounded))
+ .contentTransition(.numericText())
+ Text("Schritte heute")
+ .font(.subheadline)
+ .foregroundStyle(.secondary)
+ if manager.progress >= 1.0 {
+ Label("Ziel erreicht!", systemImage: "checkmark.circle.fill")
+ .font(.caption)
+ .fontWeight(.semibold)
+ .foregroundStyle(.green)
+ }
+ }
+ }
+ .frame(width: 260, height: 260)
+ .padding(.top, 8)
+ }
+
+ private var statsGrid: some View {
+ LazyVGrid(
+ columns: [GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible())],
+ spacing: 12
+ ) {
+ StatCard(icon: "flag.fill", color: .orange, value: "\(Int(manager.progress * 100)) %", label: "Fortschritt")
+ StatCard(icon: "figure.walk", color: .blue, value: String(format: "%.2f km", manager.distanceKm), label: "Distanz")
+ StatCard(icon: "flame.fill", color: .red, value: "\(manager.estimatedKcal) kcal", label: "Kalorien")
+ }
+ .padding(.horizontal)
+ }
+
+ private var goalCard: some View {
+ VStack(alignment: .leading, spacing: 10) {
+ HStack {
+ Label("Tagesziel", systemImage: "target")
+ .font(.headline)
+ Spacer()
+ Button("Anpassen") {
+ goalDraft = Double(manager.stepGoal)
+ showGoalSheet = true
+ }
+ .buttonStyle(.bordered)
+ .tint(.blue)
+ }
+ ProgressView(value: manager.progress)
+ .tint(ringColor)
+ Text("\(manager.todaySteps.formatted()) / \(manager.stepGoal.formatted()) Schritte")
+ .font(.caption)
+ .foregroundStyle(.secondary)
+ }
+ .padding()
+ .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 16))
+ .padding(.horizontal)
+ }
+
+ private var deniedBanner: some View {
+ ContentUnavailableView(
+ "HealthKit-Zugriff verweigert",
+ systemImage: "heart.slash",
+ description: Text("Erlaube den Zugriff in Einstellungen > Datenschutz > Health.")
+ )
+ .padding(.horizontal)
+ }
+}
+
+// MARK: - Stat Card
+
+struct StatCard: View {
+ let icon: String
+ let color: Color
+ let value: String
+ let label: String
+
+ var body: some View {
+ VStack(spacing: 6) {
+ Image(systemName: icon)
+ .font(.title3)
+ .foregroundStyle(color)
+ Text(value)
+ .font(.subheadline)
+ .fontWeight(.semibold)
+ .minimumScaleFactor(0.6)
+ .lineLimit(1)
+ Text(label)
+ .font(.caption2)
+ .foregroundStyle(.secondary)
+ }
+ .frame(maxWidth: .infinity)
+ .padding(.vertical, 14)
+ .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 14))
+ }
+}
+
+// MARK: - Goal Editor Sheet
+
+struct GoalEditorSheet: View {
+ @Binding var goal: Double
+ @Environment(\.dismiss) private var dismiss
+ let onSave: () -> Void
+
+ var body: some View {
+ NavigationStack {
+ Form {
+ Section("Tagesziel in Schritten") {
+ Slider(value: $goal, in: 1_000...30_000, step: 500) {
+ Text("Ziel")
+ } minimumValueLabel: {
+ Text("1K").font(.caption)
+ } maximumValueLabel: {
+ Text("30K").font(.caption)
+ }
+ Text("\(Int(goal).formatted()) Schritte")
+ .font(.title2.bold())
+ .frame(maxWidth: .infinity, alignment: .center)
+ .padding(.vertical, 4)
+ }
+ }
+ .navigationTitle("Ziel anpassen")
+ .navigationBarTitleDisplayMode(.inline)
+ .toolbar {
+ ToolbarItem(placement: .cancellationAction) { Button("Abbrechen") { dismiss() } }
+ ToolbarItem(placement: .confirmationAction) {
+ Button("Speichern") { onSave(); dismiss() }
+ }
+ }
+ }
+ .presentationDetents([.medium])
+ }
+}
+
+#Preview {
+ ContentView()
+}
diff --git a/examples/ios-widget-steps/StepsWidget/StepsApp/HealthKitManager.swift b/examples/ios-widget-steps/StepsWidget/StepsApp/HealthKitManager.swift
new file mode 100644
index 0000000000..2a5518d0ee
--- /dev/null
+++ b/examples/ios-widget-steps/StepsWidget/StepsApp/HealthKitManager.swift
@@ -0,0 +1,80 @@
+import HealthKit
+import WidgetKit
+
+// MARK: - HealthKit Manager
+
+@MainActor
+final class HealthKitManager: ObservableObject {
+ private let store = HKHealthStore()
+
+ @Published var todaySteps: Int = 0
+ @Published var stepGoal: Int = 10_000
+ @Published var authorizationStatus: AuthStatus = .notDetermined
+
+ enum AuthStatus { case notDetermined, authorized, denied }
+
+ private static let appGroup = "group.com.example.stepswidget"
+ private static let goalKey = "stepGoal"
+
+ init() {
+ let stored = UserDefaults(suiteName: Self.appGroup)?.integer(forKey: Self.goalKey) ?? 0
+ stepGoal = stored > 0 ? stored : 10_000
+ }
+
+ // MARK: - Authorization
+
+ func requestAuthorization() {
+ guard HKHealthStore.isHealthDataAvailable() else {
+ authorizationStatus = .denied
+ return
+ }
+ let stepType = HKQuantityType(.stepCount)
+ store.requestAuthorization(toShare: [], read: [stepType]) { [weak self] success, _ in
+ Task { @MainActor [weak self] in
+ self?.authorizationStatus = success ? .authorized : .denied
+ if success { await self?.fetchTodaySteps() }
+ }
+ }
+ }
+
+ // MARK: - Step Query
+
+ func fetchTodaySteps() async {
+ guard HKHealthStore.isHealthDataAvailable() else { return }
+ let stepType = HKQuantityType(.stepCount)
+ let start = Calendar.current.startOfDay(for: .now)
+ let pred = HKQuery.predicateForSamples(withStart: start, end: .now, options: .strictStartDate)
+
+ let steps: Int = await withCheckedContinuation { continuation in
+ let query = HKStatisticsQuery(
+ quantityType: stepType,
+ quantitySamplePredicate: pred,
+ options: .cumulativeSum
+ ) { _, result, _ in
+ let count = Int(result?.sumQuantity()?.doubleValue(for: .count()) ?? 0)
+ continuation.resume(returning: count)
+ }
+ store.execute(query)
+ }
+ todaySteps = steps
+ }
+
+ // MARK: - Goal
+
+ func updateGoal(_ newGoal: Int) {
+ stepGoal = newGoal
+ UserDefaults(suiteName: Self.appGroup)?.set(newGoal, forKey: Self.goalKey)
+ WidgetCenter.shared.reloadTimelines(ofKind: "StepsWidget")
+ }
+
+ // MARK: - Derived values
+
+ var progress: Double {
+ guard stepGoal > 0 else { return 0 }
+ return min(Double(todaySteps) / Double(stepGoal), 1.0)
+ }
+
+ var distanceKm: Double { Double(todaySteps) * 0.000762 }
+
+ var estimatedKcal: Int { Int(Double(todaySteps) * 0.04) }
+}
diff --git a/examples/ios-widget-steps/StepsWidget/StepsApp/Info.plist b/examples/ios-widget-steps/StepsWidget/StepsApp/Info.plist
new file mode 100644
index 0000000000..060331ecbe
--- /dev/null
+++ b/examples/ios-widget-steps/StepsWidget/StepsApp/Info.plist
@@ -0,0 +1,32 @@
+
+
+
+
+ CFBundleShortVersionString
+ $(MARKETING_VERSION)
+ CFBundleVersion
+ $(CURRENT_PROJECT_VERSION)
+ NSHealthShareUsageDescription
+ StepsWidget liest deine Schrittzähler-Daten aus Apple Health, um sie in der App und im Widget anzuzeigen.
+ UIApplicationSceneManifest
+
+ UIApplicationSupportsMultipleScenes
+
+
+ UILaunchScreen
+
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+
+
diff --git a/examples/ios-widget-steps/StepsWidget/StepsApp/StepsApp.entitlements b/examples/ios-widget-steps/StepsWidget/StepsApp/StepsApp.entitlements
new file mode 100644
index 0000000000..96229d19d1
--- /dev/null
+++ b/examples/ios-widget-steps/StepsWidget/StepsApp/StepsApp.entitlements
@@ -0,0 +1,14 @@
+
+
+
+
+ com.apple.developer.healthkit
+
+ com.apple.developer.healthkit.background-delivery
+
+ com.apple.security.application-groups
+
+ group.com.example.stepswidget
+
+
+
diff --git a/examples/ios-widget-steps/StepsWidget/StepsApp/StepsApp.swift b/examples/ios-widget-steps/StepsWidget/StepsApp/StepsApp.swift
new file mode 100644
index 0000000000..c6f2b71764
--- /dev/null
+++ b/examples/ios-widget-steps/StepsWidget/StepsApp/StepsApp.swift
@@ -0,0 +1,10 @@
+import SwiftUI
+
+@main
+struct StepsApp: App {
+ var body: some Scene {
+ WindowGroup {
+ ContentView()
+ }
+ }
+}
diff --git a/examples/ios-widget-steps/StepsWidget/StepsWidget.xcodeproj/project.pbxproj b/examples/ios-widget-steps/StepsWidget/StepsWidget.xcodeproj/project.pbxproj
new file mode 100644
index 0000000000..c697c2b7a0
--- /dev/null
+++ b/examples/ios-widget-steps/StepsWidget/StepsWidget.xcodeproj/project.pbxproj
@@ -0,0 +1,521 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 77;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ AA0000000000000000000211 /* StepsApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA0000000000000000000011 /* StepsApp.swift */; };
+ AA0000000000000000000212 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA0000000000000000000012 /* ContentView.swift */; };
+ AA0000000000000000000213 /* HealthKitManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA0000000000000000000013 /* HealthKitManager.swift */; };
+ AA0000000000000000000214 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AA0000000000000000000014 /* Assets.xcassets */; };
+ AA0000000000000000000215 /* StepsWidgetBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA0000000000000000000017 /* StepsWidgetBundle.swift */; };
+ AA0000000000000000000216 /* StepsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA0000000000000000000018 /* StepsProvider.swift */; };
+ AA0000000000000000000217 /* StepsWidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA0000000000000000000019 /* StepsWidgetView.swift */; };
+ AA0000000000000000000218 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AA000000000000000000001A /* Assets.xcassets */; };
+ AA0000000000000000000219 /* HealthKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA000000000000000000001D /* HealthKit.framework */; };
+ AA000000000000000000021A /* HealthKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA000000000000000000001D /* HealthKit.framework */; };
+ AA000000000000000000021B /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA000000000000000000001E /* WidgetKit.framework */; };
+ AA000000000000000000021C /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA000000000000000000001F /* SwiftUI.framework */; };
+ AA000000000000000000021D /* StepsWidgetExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = AA0000000000000000000021 /* StepsWidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
+ AA000000000000000000021E /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AA000000000000000000001E /* WidgetKit.framework */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXContainerItemProxy section */
+ AA0000000000000000000072 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = AA0000000000000000000001 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = AA0000000000000000000003;
+ remoteInfo = StepsWidgetExtension;
+ };
+/* End PBXContainerItemProxy section */
+
+/* Begin PBXCopyFilesBuildPhase section */
+ AA0000000000000000000044 /* Embed Foundation Extensions */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = "";
+ dstSubfolderSpec = 13;
+ files = (
+ AA000000000000000000021D /* StepsWidgetExtension.appex in Embed Foundation Extensions */,
+ );
+ name = "Embed Foundation Extensions";
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXCopyFilesBuildPhase section */
+
+/* Begin PBXFileReference section */
+ AA0000000000000000000011 /* StepsApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepsApp.swift; sourceTree = ""; };
+ AA0000000000000000000012 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; };
+ AA0000000000000000000013 /* HealthKitManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthKitManager.swift; sourceTree = ""; };
+ AA0000000000000000000014 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
+ AA0000000000000000000015 /* StepsApp.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = StepsApp.entitlements; sourceTree = ""; };
+ AA0000000000000000000016 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ AA0000000000000000000017 /* StepsWidgetBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepsWidgetBundle.swift; sourceTree = ""; };
+ AA0000000000000000000018 /* StepsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepsProvider.swift; sourceTree = ""; };
+ AA0000000000000000000019 /* StepsWidgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StepsWidgetView.swift; sourceTree = ""; };
+ AA000000000000000000001A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
+ AA000000000000000000001B /* StepsWidgetExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = StepsWidgetExtension.entitlements; sourceTree = ""; };
+ AA000000000000000000001C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ AA000000000000000000001D /* HealthKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = HealthKit.framework; path = System/Library/Frameworks/HealthKit.framework; sourceTree = SDKROOT; };
+ AA000000000000000000001E /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; };
+ AA000000000000000000001F /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; };
+ AA0000000000000000000020 /* StepsApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = StepsApp.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ AA0000000000000000000021 /* StepsWidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = StepsWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ AA0000000000000000000042 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ AA0000000000000000000219 /* HealthKit.framework in Frameworks */,
+ AA000000000000000000021E /* WidgetKit.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ AA0000000000000000000046 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ AA000000000000000000021A /* HealthKit.framework in Frameworks */,
+ AA000000000000000000021B /* WidgetKit.framework in Frameworks */,
+ AA000000000000000000021C /* SwiftUI.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ AA0000000000000000000031 = {
+ isa = PBXGroup;
+ children = (
+ AA0000000000000000000032 /* StepsApp */,
+ AA0000000000000000000033 /* StepsWidgetExtension */,
+ AA0000000000000000000034 /* Frameworks */,
+ AA0000000000000000000035 /* Products */,
+ );
+ sourceTree = "";
+ };
+ AA0000000000000000000032 /* StepsApp */ = {
+ isa = PBXGroup;
+ children = (
+ AA0000000000000000000011 /* StepsApp.swift */,
+ AA0000000000000000000012 /* ContentView.swift */,
+ AA0000000000000000000013 /* HealthKitManager.swift */,
+ AA0000000000000000000014 /* Assets.xcassets */,
+ AA0000000000000000000015 /* StepsApp.entitlements */,
+ AA0000000000000000000016 /* Info.plist */,
+ );
+ path = StepsApp;
+ sourceTree = "";
+ };
+ AA0000000000000000000033 /* StepsWidgetExtension */ = {
+ isa = PBXGroup;
+ children = (
+ AA0000000000000000000017 /* StepsWidgetBundle.swift */,
+ AA0000000000000000000018 /* StepsProvider.swift */,
+ AA0000000000000000000019 /* StepsWidgetView.swift */,
+ AA000000000000000000001A /* Assets.xcassets */,
+ AA000000000000000000001B /* StepsWidgetExtension.entitlements */,
+ AA000000000000000000001C /* Info.plist */,
+ );
+ path = StepsWidgetExtension;
+ sourceTree = "";
+ };
+ AA0000000000000000000034 /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ AA000000000000000000001D /* HealthKit.framework */,
+ AA000000000000000000001E /* WidgetKit.framework */,
+ AA000000000000000000001F /* SwiftUI.framework */,
+ );
+ name = Frameworks;
+ sourceTree = "";
+ };
+ AA0000000000000000000035 /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ AA0000000000000000000020 /* StepsApp.app */,
+ AA0000000000000000000021 /* StepsWidgetExtension.appex */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ AA0000000000000000000002 /* StepsApp */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = AA0000000000000000000062 /* Build configuration list for PBXNativeTarget "StepsApp" */;
+ buildPhases = (
+ AA0000000000000000000041 /* Sources */,
+ AA0000000000000000000042 /* Frameworks */,
+ AA0000000000000000000043 /* Resources */,
+ AA0000000000000000000044 /* Embed Foundation Extensions */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ AA0000000000000000000071 /* PBXTargetDependency */,
+ );
+ name = StepsApp;
+ productName = StepsApp;
+ productReference = AA0000000000000000000020 /* StepsApp.app */;
+ productType = "com.apple.product-type.application";
+ };
+ AA0000000000000000000003 /* StepsWidgetExtension */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = AA0000000000000000000063 /* Build configuration list for PBXNativeTarget "StepsWidgetExtension" */;
+ buildPhases = (
+ AA0000000000000000000045 /* Sources */,
+ AA0000000000000000000046 /* Frameworks */,
+ AA0000000000000000000047 /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = StepsWidgetExtension;
+ productName = StepsWidgetExtension;
+ productReference = AA0000000000000000000021 /* StepsWidgetExtension.appex */;
+ productType = "com.apple.product-type.app-extension";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ AA0000000000000000000001 /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ BuildIndependentTargetsInParallel = 1;
+ LastSwiftUpdateCheck = 1600;
+ LastUpgradeCheck = 1600;
+ TargetAttributes = {
+ AA0000000000000000000002 = {
+ CreatedOnToolsVersion = 16.0;
+ };
+ AA0000000000000000000003 = {
+ CreatedOnToolsVersion = 16.0;
+ };
+ };
+ };
+ buildConfigurationList = AA0000000000000000000061 /* Build configuration list for PBXProject "StepsWidget" */;
+ compatibilityVersion = "Xcode 14.0";
+ developmentRegion = en;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ Base,
+ );
+ mainGroup = AA0000000000000000000031;
+ productRefGroup = AA0000000000000000000035 /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ AA0000000000000000000002 /* StepsApp */,
+ AA0000000000000000000003 /* StepsWidgetExtension */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ AA0000000000000000000043 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ AA0000000000000000000214 /* Assets.xcassets in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ AA0000000000000000000047 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ AA0000000000000000000218 /* Assets.xcassets in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ AA0000000000000000000041 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ AA0000000000000000000211 /* StepsApp.swift in Sources */,
+ AA0000000000000000000212 /* ContentView.swift in Sources */,
+ AA0000000000000000000213 /* HealthKitManager.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ AA0000000000000000000045 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ AA0000000000000000000215 /* StepsWidgetBundle.swift in Sources */,
+ AA0000000000000000000216 /* StepsProvider.swift in Sources */,
+ AA0000000000000000000217 /* StepsWidgetView.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXTargetDependency section */
+ AA0000000000000000000071 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = AA0000000000000000000003 /* StepsWidgetExtension */;
+ targetProxy = AA0000000000000000000072 /* PBXContainerItemProxy */;
+ };
+/* End PBXTargetDependency section */
+
+/* Begin XCBuildConfiguration section */
+ AA0000000000000000000051 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu17;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 26.0;
+ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+ MTL_FAST_MATH = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ SDKROOT = iphoneos;
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ };
+ name = Debug;
+ };
+ AA0000000000000000000052 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu17;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 26.0;
+ MTL_FAST_MATH = YES;
+ SDKROOT = iphoneos;
+ SWIFT_COMPILATION_MODE = wholemodule;
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Release;
+ };
+ AA0000000000000000000053 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CODE_SIGN_ENTITLEMENTS = StepsApp/StepsApp.entitlements;
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ GENERATE_INFOPLIST_FILE = NO;
+ INFOPLIST_FILE = StepsApp/Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 26.0;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = "com.example.stepswidget";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 6.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Debug;
+ };
+ AA0000000000000000000054 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CODE_SIGN_ENTITLEMENTS = StepsApp/StepsApp.entitlements;
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ GENERATE_INFOPLIST_FILE = NO;
+ INFOPLIST_FILE = StepsApp/Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 26.0;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = "com.example.stepswidget";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 6.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Release;
+ };
+ AA0000000000000000000055 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ APPLICATION_EXTENSION_API_ONLY = YES;
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
+ CODE_SIGN_ENTITLEMENTS = StepsWidgetExtension/StepsWidgetExtension.entitlements;
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ GENERATE_INFOPLIST_FILE = NO;
+ INFOPLIST_FILE = StepsWidgetExtension/Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 26.0;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@executable_path/../../Frameworks",
+ );
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = "com.example.stepswidget.widget";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SKIP_INSTALL = YES;
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 6.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Debug;
+ };
+ AA0000000000000000000056 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ APPLICATION_EXTENSION_API_ONLY = YES;
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
+ CODE_SIGN_ENTITLEMENTS = StepsWidgetExtension/StepsWidgetExtension.entitlements;
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ GENERATE_INFOPLIST_FILE = NO;
+ INFOPLIST_FILE = StepsWidgetExtension/Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 26.0;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@executable_path/../../Frameworks",
+ );
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = "com.example.stepswidget.widget";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SKIP_INSTALL = YES;
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 6.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ AA0000000000000000000061 /* Build configuration list for PBXProject "StepsWidget" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ AA0000000000000000000051 /* Debug */,
+ AA0000000000000000000052 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ AA0000000000000000000062 /* Build configuration list for PBXNativeTarget "StepsApp" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ AA0000000000000000000053 /* Debug */,
+ AA0000000000000000000054 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ AA0000000000000000000063 /* Build configuration list for PBXNativeTarget "StepsWidgetExtension" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ AA0000000000000000000055 /* Debug */,
+ AA0000000000000000000056 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+
+ };
+ rootObject = AA0000000000000000000001 /* Project object */;
+}
diff --git a/examples/ios-widget-steps/StepsWidget/StepsWidgetExtension/Assets.xcassets/AccentColor.colorset/Contents.json b/examples/ios-widget-steps/StepsWidget/StepsWidgetExtension/Assets.xcassets/AccentColor.colorset/Contents.json
new file mode 100644
index 0000000000..805e83152a
--- /dev/null
+++ b/examples/ios-widget-steps/StepsWidget/StepsWidgetExtension/Assets.xcassets/AccentColor.colorset/Contents.json
@@ -0,0 +1,38 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "1.000",
+ "green" : "0.478",
+ "red" : "0.000"
+ }
+ },
+ "idiom" : "universal"
+ },
+ {
+ "appearances" : [
+ {
+ "appearance" : "luminosity",
+ "value" : "dark"
+ }
+ ],
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "1.000",
+ "green" : "0.478",
+ "red" : "0.000"
+ }
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/examples/ios-widget-steps/StepsWidget/StepsWidgetExtension/Assets.xcassets/Contents.json b/examples/ios-widget-steps/StepsWidget/StepsWidgetExtension/Assets.xcassets/Contents.json
new file mode 100644
index 0000000000..73c00596a7
--- /dev/null
+++ b/examples/ios-widget-steps/StepsWidget/StepsWidgetExtension/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/examples/ios-widget-steps/StepsWidget/StepsWidgetExtension/Assets.xcassets/WidgetBackground.colorset/Contents.json b/examples/ios-widget-steps/StepsWidget/StepsWidgetExtension/Assets.xcassets/WidgetBackground.colorset/Contents.json
new file mode 100644
index 0000000000..fe3de53757
--- /dev/null
+++ b/examples/ios-widget-steps/StepsWidget/StepsWidgetExtension/Assets.xcassets/WidgetBackground.colorset/Contents.json
@@ -0,0 +1,38 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "1.000",
+ "green" : "1.000",
+ "red" : "1.000"
+ }
+ },
+ "idiom" : "universal"
+ },
+ {
+ "appearances" : [
+ {
+ "appearance" : "luminosity",
+ "value" : "dark"
+ }
+ ],
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0.118",
+ "green" : "0.118",
+ "red" : "0.118"
+ }
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/examples/ios-widget-steps/StepsWidget/StepsWidgetExtension/Info.plist b/examples/ios-widget-steps/StepsWidget/StepsWidgetExtension/Info.plist
new file mode 100644
index 0000000000..1cad92f721
--- /dev/null
+++ b/examples/ios-widget-steps/StepsWidget/StepsWidgetExtension/Info.plist
@@ -0,0 +1,15 @@
+
+
+
+
+ CFBundleShortVersionString
+ $(MARKETING_VERSION)
+ CFBundleVersion
+ $(CURRENT_PROJECT_VERSION)
+ NSExtension
+
+ NSExtensionPointIdentifier
+ com.apple.widgetkit-extension
+
+
+
diff --git a/examples/ios-widget-steps/StepsWidget/StepsWidgetExtension/StepsProvider.swift b/examples/ios-widget-steps/StepsWidget/StepsWidgetExtension/StepsProvider.swift
new file mode 100644
index 0000000000..6429f29645
--- /dev/null
+++ b/examples/ios-widget-steps/StepsWidget/StepsWidgetExtension/StepsProvider.swift
@@ -0,0 +1,86 @@
+import WidgetKit
+@preconcurrency import HealthKit
+
+// MARK: - Timeline Entry
+
+struct StepsEntry: TimelineEntry {
+ let date: Date
+ let steps: Int
+ let goal: Int
+
+ var progress: Double {
+ guard goal > 0 else { return 0 }
+ return min(Double(steps) / Double(goal), 1.0)
+ }
+
+ var distanceKm: Double { Double(steps) * 0.000762 }
+ var isGoalReached: Bool { steps >= goal }
+
+ static let placeholder = StepsEntry(date: .now, steps: 7_432, goal: 10_000)
+}
+
+// MARK: - Timeline Provider
+
+struct StepsProvider: TimelineProvider {
+ private static let appGroup = "group.com.example.stepswidget"
+ private static let goalKey = "stepGoal"
+
+ func placeholder(in context: Context) -> StepsEntry { .placeholder }
+
+ func getSnapshot(in context: Context, completion: @escaping (StepsEntry) -> Void) {
+ guard !context.isPreview else { completion(.placeholder); return }
+ fetchEntry(completion: completion)
+ }
+
+ func getTimeline(in context: Context, completion: @escaping (Timeline) -> Void) {
+ fetchEntry { entry in
+ let next = Calendar.current.date(byAdding: .minute, value: 15, to: .now)!
+ completion(Timeline(entries: [entry], policy: .after(next)))
+ }
+ }
+
+ // MARK: - Private
+
+ private func fetchEntry(completion: @escaping (StepsEntry) -> Void) {
+ let goal = loadGoal()
+
+ guard HKHealthStore.isHealthDataAvailable() else {
+ completion(StepsEntry(date: .now, steps: 0, goal: goal))
+ return
+ }
+
+ let store = HKHealthStore()
+ let stepType = HKQuantityType(.stepCount)
+
+ store.requestAuthorization(toShare: [], read: [stepType]) { success, _ in
+ guard success else {
+ completion(StepsEntry(date: .now, steps: 0, goal: goal))
+ return
+ }
+ querySteps(store: store) { steps in
+ completion(StepsEntry(date: .now, steps: steps, goal: goal))
+ }
+ }
+ }
+
+ private func querySteps(store: HKHealthStore, completion: @escaping (Int) -> Void) {
+ let stepType = HKQuantityType(.stepCount)
+ let start = Calendar.current.startOfDay(for: Date())
+ let pred = HKQuery.predicateForSamples(withStart: start, end: Date(), options: .strictStartDate)
+
+ let query = HKStatisticsQuery(
+ quantityType: stepType,
+ quantitySamplePredicate: pred,
+ options: .cumulativeSum
+ ) { _, result, _ in
+ let steps = Int(result?.sumQuantity()?.doubleValue(for: .count()) ?? 0)
+ completion(steps)
+ }
+ store.execute(query)
+ }
+
+ private func loadGoal() -> Int {
+ let stored = UserDefaults(suiteName: Self.appGroup)?.integer(forKey: Self.goalKey) ?? 0
+ return stored > 0 ? stored : 10_000
+ }
+}
diff --git a/examples/ios-widget-steps/StepsWidget/StepsWidgetExtension/StepsWidgetBundle.swift b/examples/ios-widget-steps/StepsWidget/StepsWidgetExtension/StepsWidgetBundle.swift
new file mode 100644
index 0000000000..7c2ca3038c
--- /dev/null
+++ b/examples/ios-widget-steps/StepsWidget/StepsWidgetExtension/StepsWidgetBundle.swift
@@ -0,0 +1,9 @@
+import WidgetKit
+import SwiftUI
+
+@main
+struct StepsWidgetBundle: WidgetBundle {
+ var body: some Widget {
+ StepsWidget()
+ }
+}
diff --git a/examples/ios-widget-steps/StepsWidget/StepsWidgetExtension/StepsWidgetExtension.entitlements b/examples/ios-widget-steps/StepsWidget/StepsWidgetExtension/StepsWidgetExtension.entitlements
new file mode 100644
index 0000000000..107b9a68bc
--- /dev/null
+++ b/examples/ios-widget-steps/StepsWidget/StepsWidgetExtension/StepsWidgetExtension.entitlements
@@ -0,0 +1,12 @@
+
+
+
+
+ com.apple.developer.healthkit
+
+ com.apple.security.application-groups
+
+ group.com.example.stepswidget
+
+
+
diff --git a/examples/ios-widget-steps/StepsWidget/StepsWidgetExtension/StepsWidgetView.swift b/examples/ios-widget-steps/StepsWidget/StepsWidgetExtension/StepsWidgetView.swift
new file mode 100644
index 0000000000..18c572fd1f
--- /dev/null
+++ b/examples/ios-widget-steps/StepsWidget/StepsWidgetExtension/StepsWidgetView.swift
@@ -0,0 +1,234 @@
+import SwiftUI
+import WidgetKit
+
+// MARK: - Widget Configuration
+
+struct StepsWidget: Widget {
+ let kind = "StepsWidget"
+
+ var body: some WidgetConfiguration {
+ StaticConfiguration(kind: kind, provider: StepsProvider()) { entry in
+ StepsWidgetRootView(entry: entry)
+ .containerBackground(.fill.tertiary, for: .widget)
+ }
+ .configurationDisplayName("Schritte")
+ .description("Zeigt deine heutigen Schritte aus Apple Health.")
+ .supportedFamilies([
+ .systemSmall,
+ .systemMedium,
+ .accessoryCircular,
+ .accessoryRectangular,
+ .accessoryInline,
+ ])
+ }
+}
+
+// MARK: - Root View
+
+struct StepsWidgetRootView: View {
+ @Environment(\.widgetFamily) var family
+ let entry: StepsEntry
+
+ var body: some View {
+ switch family {
+ case .systemSmall: SmallStepsView(entry: entry)
+ case .systemMedium: MediumStepsView(entry: entry)
+ case .accessoryCircular: AccessoryCircularView(entry: entry)
+ case .accessoryRectangular: AccessoryRectangularView(entry: entry)
+ case .accessoryInline: AccessoryInlineView(entry: entry)
+ default: SmallStepsView(entry: entry)
+ }
+ }
+}
+
+// MARK: - Small Widget (progress ring + step count)
+
+struct SmallStepsView: View {
+ let entry: StepsEntry
+ var ringColor: Color { entry.isGoalReached ? .green : .blue }
+
+ var body: some View {
+ ZStack {
+ Circle()
+ .stroke(ringColor.opacity(0.18), lineWidth: 12)
+ .padding(14)
+ Circle()
+ .trim(from: 0, to: entry.progress)
+ .stroke(ringColor, style: StrokeStyle(lineWidth: 12, lineCap: .round))
+ .rotationEffect(.degrees(-90))
+ .padding(14)
+
+ VStack(spacing: 2) {
+ Image(systemName: "figure.walk")
+ .font(.caption2)
+ .foregroundStyle(ringColor)
+ Text(stepsLabel)
+ .font(.system(size: 22, weight: .bold, design: .rounded))
+ .minimumScaleFactor(0.5)
+ .lineLimit(1)
+ Text("Schritte")
+ .font(.system(size: 9))
+ .foregroundStyle(.secondary)
+ if entry.isGoalReached {
+ Image(systemName: "checkmark.circle.fill")
+ .font(.caption2)
+ .foregroundStyle(.green)
+ .padding(.top, 1)
+ }
+ }
+ }
+ }
+
+ private var stepsLabel: String {
+ entry.steps >= 10_000
+ ? String(format: "%.1fK", Double(entry.steps) / 1_000)
+ : entry.steps.formatted()
+ }
+}
+
+// MARK: - Medium Widget (ring + stats)
+
+struct MediumStepsView: View {
+ let entry: StepsEntry
+ var ringColor: Color { entry.isGoalReached ? .green : .blue }
+
+ var body: some View {
+ HStack(spacing: 16) {
+ // Progress ring
+ ZStack {
+ Circle()
+ .stroke(ringColor.opacity(0.18), lineWidth: 11)
+ Circle()
+ .trim(from: 0, to: entry.progress)
+ .stroke(ringColor, style: StrokeStyle(lineWidth: 11, lineCap: .round))
+ .rotationEffect(.degrees(-90))
+ VStack(spacing: 1) {
+ Text(entry.steps.formatted())
+ .font(.system(size: 18, weight: .bold, design: .rounded))
+ .minimumScaleFactor(0.5)
+ .lineLimit(1)
+ Text("\(Int(entry.progress * 100)) %")
+ .font(.caption2)
+ .foregroundStyle(.secondary)
+ }
+ }
+ .frame(width: 90, height: 90)
+
+ // Stats
+ VStack(alignment: .leading, spacing: 7) {
+ StepStatRow(icon: "target", color: .orange, text: "Ziel: \(entry.goal.formatted())")
+ StepStatRow(icon: "figure.walk", color: .blue, text: String(format: "%.2f km", entry.distanceKm))
+ ProgressView(value: entry.progress).tint(ringColor)
+
+ HStack {
+ if entry.isGoalReached {
+ Label("Ziel erreicht!", systemImage: "star.fill")
+ .font(.caption2).fontWeight(.semibold).foregroundStyle(.green)
+ }
+ Spacer()
+ Text(entry.date, style: .relative)
+ .font(.caption2).foregroundStyle(.tertiary)
+ }
+ }
+ .frame(maxWidth: .infinity, alignment: .leading)
+ }
+ .padding(14)
+ }
+}
+
+private struct StepStatRow: View {
+ let icon: String
+ let color: Color
+ let text: String
+
+ var body: some View {
+ Label {
+ Text(text).font(.caption)
+ } icon: {
+ Image(systemName: icon).foregroundStyle(color)
+ }
+ }
+}
+
+// MARK: - Accessory Circular (Lock Screen – Gauge)
+
+struct AccessoryCircularView: View {
+ let entry: StepsEntry
+
+ var body: some View {
+ Gauge(value: entry.progress) {
+ Image(systemName: "figure.walk")
+ } currentValueLabel: {
+ Text(compactSteps)
+ .font(.system(size: 11, weight: .bold, design: .rounded))
+ }
+ .gaugeStyle(.accessoryCircular)
+ .tint(entry.isGoalReached ? .green : .blue)
+ }
+
+ private var compactSteps: String {
+ entry.steps >= 1_000
+ ? String(format: "%.1fK", Double(entry.steps) / 1_000)
+ : "\(entry.steps)"
+ }
+}
+
+// MARK: - Accessory Rectangular (Lock Screen)
+
+struct AccessoryRectangularView: View {
+ let entry: StepsEntry
+
+ var body: some View {
+ VStack(alignment: .leading, spacing: 2) {
+ Label("Schritte heute", systemImage: "figure.walk")
+ .font(.caption2)
+ .foregroundStyle(.secondary)
+ Text(entry.steps.formatted())
+ .font(.system(.headline, design: .rounded))
+ .fontWeight(.bold)
+ ProgressView(value: entry.progress)
+ .tint(entry.isGoalReached ? .green : .blue)
+ }
+ }
+}
+
+// MARK: - Accessory Inline (Lock Screen)
+
+struct AccessoryInlineView: View {
+ let entry: StepsEntry
+
+ var body: some View {
+ Label(
+ "\(entry.steps.formatted()) / \(entry.goal.formatted())",
+ systemImage: "figure.walk"
+ )
+ }
+}
+
+// MARK: - Previews
+
+#Preview("Small", as: .systemSmall) {
+ StepsWidget()
+} timeline: {
+ StepsEntry.placeholder
+ StepsEntry(date: .now, steps: 10_823, goal: 10_000)
+}
+
+#Preview("Medium", as: .systemMedium) {
+ StepsWidget()
+} timeline: {
+ StepsEntry.placeholder
+ StepsEntry(date: .now, steps: 10_823, goal: 10_000)
+}
+
+#Preview("Circular", as: .accessoryCircular) {
+ StepsWidget()
+} timeline: {
+ StepsEntry.placeholder
+}
+
+#Preview("Rectangular", as: .accessoryRectangular) {
+ StepsWidget()
+} timeline: {
+ StepsEntry.placeholder
+}
diff --git a/examples/ios-widget/ClaudeWidget/ClaudeWidget.xcodeproj/project.pbxproj b/examples/ios-widget/ClaudeWidget/ClaudeWidget.xcodeproj/project.pbxproj
new file mode 100644
index 0000000000..7f25c5b16f
--- /dev/null
+++ b/examples/ios-widget/ClaudeWidget/ClaudeWidget.xcodeproj/project.pbxproj
@@ -0,0 +1,387 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 77;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ A11A000001 /* ClaudeWidgetBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = A10A000001 /* ClaudeWidgetBundle.swift */; };
+ A11A000002 /* WidgetData.swift in Sources */ = {isa = PBXBuildFile; fileRef = A10A000002 /* WidgetData.swift */; };
+ A11A000003 /* ClaudeStatusProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A10A000003 /* ClaudeStatusProvider.swift */; };
+ A11A000004 /* ClaudeSessionProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A10A000004 /* ClaudeSessionProvider.swift */; };
+ A11A000005 /* ClaudeStatusWidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A10A000005 /* ClaudeStatusWidgetView.swift */; };
+ A11A000006 /* ClaudeSessionWidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A10A000006 /* ClaudeSessionWidgetView.swift */; };
+ A11A000007 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A10A000007 /* Assets.xcassets */; };
+ A11A000008 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A10A000008 /* WidgetKit.framework */; };
+ A11A000009 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A10A000009 /* SwiftUI.framework */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXFileReference section */
+ A10A000001 /* ClaudeWidgetBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClaudeWidgetBundle.swift; sourceTree = ""; };
+ A10A000002 /* WidgetData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetData.swift; sourceTree = ""; };
+ A10A000003 /* ClaudeStatusProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClaudeStatusProvider.swift; sourceTree = ""; };
+ A10A000004 /* ClaudeSessionProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClaudeSessionProvider.swift; sourceTree = ""; };
+ A10A000005 /* ClaudeStatusWidgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClaudeStatusWidgetView.swift; sourceTree = ""; };
+ A10A000006 /* ClaudeSessionWidgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClaudeSessionWidgetView.swift; sourceTree = ""; };
+ A10A000007 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
+ A10A000008 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; };
+ A10A000009 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; };
+ A10A00000A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ A10A00000B /* ClaudeWidget.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = ClaudeWidget.appex; sourceTree = BUILT_PRODUCTS_DIR; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ A1FB000001 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ A11A000008 /* WidgetKit.framework in Frameworks */,
+ A11A000009 /* SwiftUI.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ A1GRP00001 /* ClaudeWidget */ = {
+ isa = PBXGroup;
+ children = (
+ A1GRP00002 /* WidgetExtension */,
+ A1GRP00005 /* Frameworks */,
+ A1GRP00006 /* Products */,
+ );
+ sourceTree = "";
+ };
+ A1GRP00002 /* WidgetExtension */ = {
+ isa = PBXGroup;
+ children = (
+ A10A000001 /* ClaudeWidgetBundle.swift */,
+ A10A00000A /* Info.plist */,
+ A10A000007 /* Assets.xcassets */,
+ A1GRP00003 /* Model */,
+ A1GRP00004 /* Provider */,
+ A1GRP00007 /* Views */,
+ );
+ path = WidgetExtension;
+ sourceTree = "";
+ };
+ A1GRP00003 /* Model */ = {
+ isa = PBXGroup;
+ children = (
+ A10A000002 /* WidgetData.swift */,
+ );
+ path = Model;
+ sourceTree = "";
+ };
+ A1GRP00004 /* Provider */ = {
+ isa = PBXGroup;
+ children = (
+ A10A000003 /* ClaudeStatusProvider.swift */,
+ A10A000004 /* ClaudeSessionProvider.swift */,
+ );
+ path = Provider;
+ sourceTree = "";
+ };
+ A1GRP00007 /* Views */ = {
+ isa = PBXGroup;
+ children = (
+ A10A000005 /* ClaudeStatusWidgetView.swift */,
+ A10A000006 /* ClaudeSessionWidgetView.swift */,
+ );
+ path = Views;
+ sourceTree = "";
+ };
+ A1GRP00005 /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ A10A000008 /* WidgetKit.framework */,
+ A10A000009 /* SwiftUI.framework */,
+ );
+ name = Frameworks;
+ sourceTree = "";
+ };
+ A1GRP00006 /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ A10A00000B /* ClaudeWidget.appex */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ A1TGT00001 /* ClaudeWidgetExtension */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = A1BCL00001 /* Build configuration list for PBXNativeTarget "ClaudeWidgetExtension" */;
+ buildPhases = (
+ A1SRC00001 /* Sources */,
+ A1FB000001 /* Frameworks */,
+ A1RES00001 /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = ClaudeWidgetExtension;
+ productName = ClaudeWidget;
+ productReference = A10A00000B /* ClaudeWidget.appex */;
+ productType = "com.apple.product-type.app-extension";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ A1PRJ00001 /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ BuildIndependentTargetsInParallel = 1;
+ LastSwiftUpdateCheck = 1600;
+ LastUpgradeCheck = 1600;
+ TargetAttributes = {
+ A1TGT00001 = {
+ CreatedOnToolsVersion = 16.0;
+ };
+ };
+ };
+ buildConfigurationList = A1BCL00002 /* Build configuration list for PBXProject "ClaudeWidget" */;
+ compatibilityVersion = "Xcode 14.0";
+ developmentRegion = en;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ Base,
+ );
+ mainGroup = A1GRP00001 /* ClaudeWidget */;
+ productRefGroup = A1GRP00006 /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ A1TGT00001 /* ClaudeWidgetExtension */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ A1RES00001 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ A11A000007 /* Assets.xcassets in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ A1SRC00001 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ A11A000001 /* ClaudeWidgetBundle.swift in Sources */,
+ A11A000002 /* WidgetData.swift in Sources */,
+ A11A000003 /* ClaudeStatusProvider.swift in Sources */,
+ A11A000004 /* ClaudeSessionProvider.swift in Sources */,
+ A11A000005 /* ClaudeStatusWidgetView.swift in Sources */,
+ A11A000006 /* ClaudeSessionWidgetView.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin XCBuildConfiguration section */
+ A1BC000001 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu17;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 26.0;
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+ MTL_FAST_MATH = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ SDKROOT = iphoneos;
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ };
+ name = Debug;
+ };
+ A1BC000002 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_USER_SCRIPT_SANDBOXING = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu17;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 26.0;
+ LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
+ MTL_FAST_MATH = YES;
+ SDKROOT = iphoneos;
+ SWIFT_COMPILATION_MODE = wholemodule;
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Release;
+ };
+ A1BC000003 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ APPLICATION_EXTENSION_API_ONLY = YES;
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ GENERATE_INFOPLIST_FILE = NO;
+ INFOPLIST_FILE = WidgetExtension/Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 26.0;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@executable_path/../../Frameworks",
+ );
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = "com.anthropic.claude-code.widget";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SKIP_INSTALL = YES;
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 6.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Debug;
+ };
+ A1BC000004 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ APPLICATION_EXTENSION_API_ONLY = YES;
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ GENERATE_INFOPLIST_FILE = NO;
+ INFOPLIST_FILE = WidgetExtension/Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 26.0;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@executable_path/../../Frameworks",
+ );
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = "com.anthropic.claude-code.widget";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SKIP_INSTALL = YES;
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 6.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ A1BCL00001 /* Build configuration list for PBXNativeTarget "ClaudeWidgetExtension" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ A1BC000003 /* Debug */,
+ A1BC000004 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ A1BCL00002 /* Build configuration list for PBXProject "ClaudeWidget" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ A1BC000001 /* Debug */,
+ A1BC000002 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+
+ };
+ rootObject = A1PRJ00001 /* Project object */;
+}
diff --git a/examples/ios-widget/ClaudeWidget/WidgetExtension/Assets.xcassets/Contents.json b/examples/ios-widget/ClaudeWidget/WidgetExtension/Assets.xcassets/Contents.json
new file mode 100644
index 0000000000..73c00596a7
--- /dev/null
+++ b/examples/ios-widget/ClaudeWidget/WidgetExtension/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/examples/ios-widget/ClaudeWidget/WidgetExtension/ClaudeWidgetBundle.swift b/examples/ios-widget/ClaudeWidget/WidgetExtension/ClaudeWidgetBundle.swift
new file mode 100644
index 0000000000..6737759908
--- /dev/null
+++ b/examples/ios-widget/ClaudeWidget/WidgetExtension/ClaudeWidgetBundle.swift
@@ -0,0 +1,10 @@
+import WidgetKit
+import SwiftUI
+
+@main
+struct ClaudeWidgetBundle: WidgetBundle {
+ var body: some Widget {
+ ClaudeStatusWidget()
+ ClaudeSessionWidget()
+ }
+}
diff --git a/examples/ios-widget/ClaudeWidget/WidgetExtension/Info.plist b/examples/ios-widget/ClaudeWidget/WidgetExtension/Info.plist
new file mode 100644
index 0000000000..250057eded
--- /dev/null
+++ b/examples/ios-widget/ClaudeWidget/WidgetExtension/Info.plist
@@ -0,0 +1,11 @@
+
+
+
+
+ NSExtension
+
+ NSExtensionPointIdentifier
+ com.apple.widgetkit-extension
+
+
+
diff --git a/examples/ios-widget/ClaudeWidget/WidgetExtension/Model/WidgetData.swift b/examples/ios-widget/ClaudeWidget/WidgetExtension/Model/WidgetData.swift
new file mode 100644
index 0000000000..004ff50a3b
--- /dev/null
+++ b/examples/ios-widget/ClaudeWidget/WidgetExtension/Model/WidgetData.swift
@@ -0,0 +1,71 @@
+import Foundation
+
+// MARK: - Widget Data Models
+
+struct ClaudeSessionData: Codable {
+ let totalSessions: Int
+ let linesChanged: Int
+ let filesEdited: Int
+ let tasksCompleted: Int
+ let lastActive: Date
+ let activeProject: String
+ let modelUsed: String
+
+ static let placeholder = ClaudeSessionData(
+ totalSessions: 42,
+ linesChanged: 1_337,
+ filesEdited: 28,
+ tasksCompleted: 15,
+ lastActive: .now,
+ activeProject: "claude-code",
+ modelUsed: "claude-sonnet-4-6"
+ )
+
+ static let empty = ClaudeSessionData(
+ totalSessions: 0,
+ linesChanged: 0,
+ filesEdited: 0,
+ tasksCompleted: 0,
+ lastActive: .now,
+ activeProject: "–",
+ modelUsed: "–"
+ )
+}
+
+struct ClaudeStatusData: Codable {
+ let isConnected: Bool
+ let apiStatus: APIStatus
+ let currentModel: String
+ let remainingTokens: Int?
+ let lastUpdated: Date
+
+ enum APIStatus: String, Codable {
+ case operational = "Operational"
+ case degraded = "Degraded"
+ case outage = "Outage"
+
+ var color: String {
+ switch self {
+ case .operational: return "green"
+ case .degraded: return "yellow"
+ case .outage: return "red"
+ }
+ }
+ }
+
+ static let placeholder = ClaudeStatusData(
+ isConnected: true,
+ apiStatus: .operational,
+ currentModel: "claude-sonnet-4-6",
+ remainingTokens: 45_000,
+ lastUpdated: .now
+ )
+}
+
+// MARK: - Shared App Group
+
+extension UserDefaults {
+ static let widgetGroup = UserDefaults(
+ suiteName: "group.com.anthropic.claude-code.widget"
+ )
+}
diff --git a/examples/ios-widget/ClaudeWidget/WidgetExtension/Provider/ClaudeSessionProvider.swift b/examples/ios-widget/ClaudeWidget/WidgetExtension/Provider/ClaudeSessionProvider.swift
new file mode 100644
index 0000000000..3988b955bd
--- /dev/null
+++ b/examples/ios-widget/ClaudeWidget/WidgetExtension/Provider/ClaudeSessionProvider.swift
@@ -0,0 +1,60 @@
+import WidgetKit
+import Foundation
+
+// MARK: - Timeline Entry
+
+struct ClaudeSessionEntry: TimelineEntry {
+ let date: Date
+ let session: ClaudeSessionData
+ let isPlaceholder: Bool
+
+ static let placeholder = ClaudeSessionEntry(
+ date: .now,
+ session: .placeholder,
+ isPlaceholder: true
+ )
+}
+
+// MARK: - Timeline Provider
+
+struct ClaudeSessionProvider: TimelineProvider {
+ typealias Entry = ClaudeSessionEntry
+
+ func placeholder(in context: Context) -> ClaudeSessionEntry {
+ .placeholder
+ }
+
+ func getSnapshot(in context: Context, completion: @escaping (ClaudeSessionEntry) -> Void) {
+ let entry = ClaudeSessionEntry(
+ date: .now,
+ session: loadSession(),
+ isPlaceholder: context.isPreview
+ )
+ completion(entry)
+ }
+
+ func getTimeline(in context: Context, completion: @escaping (Timeline) -> Void) {
+ let currentDate = Date.now
+ let session = loadSession()
+
+ let entry = ClaudeSessionEntry(date: currentDate, session: session, isPlaceholder: false)
+
+ // Refresh every 30 minutes
+ let nextUpdate = Calendar.current.date(byAdding: .minute, value: 30, to: currentDate)!
+ let timeline = Timeline(entries: [entry], policy: .after(nextUpdate))
+ completion(timeline)
+ }
+
+ // MARK: - Private
+
+ private func loadSession() -> ClaudeSessionData {
+ guard
+ let defaults = UserDefaults.widgetGroup,
+ let data = defaults.data(forKey: "claudeSession"),
+ let decoded = try? JSONDecoder().decode(ClaudeSessionData.self, from: data)
+ else {
+ return .placeholder
+ }
+ return decoded
+ }
+}
diff --git a/examples/ios-widget/ClaudeWidget/WidgetExtension/Provider/ClaudeStatusProvider.swift b/examples/ios-widget/ClaudeWidget/WidgetExtension/Provider/ClaudeStatusProvider.swift
new file mode 100644
index 0000000000..c769e2ba2c
--- /dev/null
+++ b/examples/ios-widget/ClaudeWidget/WidgetExtension/Provider/ClaudeStatusProvider.swift
@@ -0,0 +1,60 @@
+import WidgetKit
+import Foundation
+
+// MARK: - Timeline Entry
+
+struct ClaudeStatusEntry: TimelineEntry {
+ let date: Date
+ let status: ClaudeStatusData
+ let isPlaceholder: Bool
+
+ static let placeholder = ClaudeStatusEntry(
+ date: .now,
+ status: .placeholder,
+ isPlaceholder: true
+ )
+}
+
+// MARK: - Timeline Provider
+
+struct ClaudeStatusProvider: TimelineProvider {
+ typealias Entry = ClaudeStatusEntry
+
+ func placeholder(in context: Context) -> ClaudeStatusEntry {
+ .placeholder
+ }
+
+ func getSnapshot(in context: Context, completion: @escaping (ClaudeStatusEntry) -> Void) {
+ let entry = ClaudeStatusEntry(
+ date: .now,
+ status: loadStatus(),
+ isPlaceholder: context.isPreview
+ )
+ completion(entry)
+ }
+
+ func getTimeline(in context: Context, completion: @escaping (Timeline) -> Void) {
+ let currentDate = Date.now
+ let status = loadStatus()
+
+ let entry = ClaudeStatusEntry(date: currentDate, status: status, isPlaceholder: false)
+
+ // Refresh every 15 minutes
+ let nextUpdate = Calendar.current.date(byAdding: .minute, value: 15, to: currentDate)!
+ let timeline = Timeline(entries: [entry], policy: .after(nextUpdate))
+ completion(timeline)
+ }
+
+ // MARK: - Private
+
+ private func loadStatus() -> ClaudeStatusData {
+ guard
+ let defaults = UserDefaults.widgetGroup,
+ let data = defaults.data(forKey: "claudeStatus"),
+ let decoded = try? JSONDecoder().decode(ClaudeStatusData.self, from: data)
+ else {
+ return .placeholder
+ }
+ return decoded
+ }
+}
diff --git a/examples/ios-widget/ClaudeWidget/WidgetExtension/Views/ClaudeSessionWidgetView.swift b/examples/ios-widget/ClaudeWidget/WidgetExtension/Views/ClaudeSessionWidgetView.swift
new file mode 100644
index 0000000000..db5ea73955
--- /dev/null
+++ b/examples/ios-widget/ClaudeWidget/WidgetExtension/Views/ClaudeSessionWidgetView.swift
@@ -0,0 +1,272 @@
+import SwiftUI
+import WidgetKit
+
+// MARK: - Session Widget
+
+struct ClaudeSessionWidget: Widget {
+ let kind = "ClaudeSessionWidget"
+
+ var body: some WidgetConfiguration {
+ StaticConfiguration(kind: kind, provider: ClaudeSessionProvider()) { entry in
+ ClaudeSessionWidgetView(entry: entry)
+ .containerBackground(.fill.tertiary, for: .widget)
+ }
+ .configurationDisplayName("Claude Session Stats")
+ .description("Shows your Claude Code session statistics at a glance.")
+ .supportedFamilies([
+ .systemSmall,
+ .systemMedium,
+ .systemLarge
+ ])
+ }
+}
+
+// MARK: - View
+
+struct ClaudeSessionWidgetView: View {
+ @Environment(\.widgetFamily) var family
+ var entry: ClaudeSessionEntry
+
+ var body: some View {
+ switch family {
+ case .systemSmall:
+ SmallSessionView(entry: entry)
+ case .systemMedium:
+ MediumSessionView(entry: entry)
+ case .systemLarge:
+ LargeSessionView(entry: entry)
+ default:
+ SmallSessionView(entry: entry)
+ }
+ }
+}
+
+// MARK: - Shared stat tile
+
+struct StatTile: View {
+ let icon: String
+ let iconColor: Color
+ let value: String
+ let label: String
+
+ var body: some View {
+ VStack(alignment: .leading, spacing: 4) {
+ Image(systemName: icon)
+ .foregroundStyle(iconColor)
+ .font(.caption)
+ Text(value)
+ .font(.headline)
+ .fontWeight(.semibold)
+ .minimumScaleFactor(0.7)
+ .lineLimit(1)
+ Text(label)
+ .font(.caption2)
+ .foregroundStyle(.secondary)
+ }
+ .padding(10)
+ .frame(maxWidth: .infinity, alignment: .leading)
+ .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 12))
+ }
+}
+
+// MARK: - Small
+
+struct SmallSessionView: View {
+ let entry: ClaudeSessionEntry
+
+ var body: some View {
+ VStack(alignment: .leading, spacing: 6) {
+ HStack {
+ Image(systemName: "terminal.fill")
+ .foregroundStyle(.purple)
+ Text("Sessions")
+ .font(.caption)
+ .foregroundStyle(.secondary)
+ Spacer()
+ }
+
+ Text("\(entry.session.totalSessions)")
+ .font(.system(size: 36, weight: .bold, design: .rounded))
+ .foregroundStyle(.purple)
+
+ Divider()
+
+ HStack {
+ Image(systemName: "doc.text.fill")
+ .foregroundStyle(.blue)
+ .font(.caption)
+ Text("\(entry.session.filesEdited) files")
+ .font(.caption)
+ Spacer()
+ Image(systemName: "checkmark.circle.fill")
+ .foregroundStyle(.green)
+ .font(.caption)
+ Text("\(entry.session.tasksCompleted) tasks")
+ .font(.caption)
+ }
+
+ Text(entry.session.activeProject)
+ .font(.caption2)
+ .foregroundStyle(.secondary)
+ .lineLimit(1)
+ }
+ .padding(12)
+ }
+}
+
+// MARK: - Medium
+
+struct MediumSessionView: View {
+ let entry: ClaudeSessionEntry
+
+ var body: some View {
+ VStack(alignment: .leading, spacing: 8) {
+ HStack {
+ Image(systemName: "terminal.fill")
+ .foregroundStyle(.purple)
+ Text("Claude Code Stats")
+ .font(.headline)
+ .fontWeight(.semibold)
+ Spacer()
+ Text(entry.session.activeProject)
+ .font(.caption)
+ .foregroundStyle(.secondary)
+ }
+
+ HStack(spacing: 8) {
+ StatTile(
+ icon: "play.circle.fill",
+ iconColor: .purple,
+ value: "\(entry.session.totalSessions)",
+ label: "Sessions"
+ )
+ StatTile(
+ icon: "doc.fill",
+ iconColor: .blue,
+ value: "\(entry.session.filesEdited)",
+ label: "Files"
+ )
+ StatTile(
+ icon: "checkmark.seal.fill",
+ iconColor: .green,
+ value: "\(entry.session.tasksCompleted)",
+ label: "Tasks"
+ )
+ StatTile(
+ icon: "pencil",
+ iconColor: .orange,
+ value: entry.session.linesChanged.formatted(.number.notation(.compactName)),
+ label: "Lines"
+ )
+ }
+ }
+ .padding(14)
+ }
+}
+
+// MARK: - Large
+
+struct LargeSessionView: View {
+ let entry: ClaudeSessionEntry
+
+ private var relativeDate: String {
+ let formatter = RelativeDateTimeFormatter()
+ formatter.unitsStyle = .short
+ return formatter.localizedString(for: entry.session.lastActive, relativeTo: .now)
+ }
+
+ var body: some View {
+ VStack(alignment: .leading, spacing: 12) {
+ // Header
+ HStack {
+ HStack(spacing: 8) {
+ Image(systemName: "terminal.fill")
+ .foregroundStyle(.purple)
+ .font(.title3)
+ VStack(alignment: .leading, spacing: 1) {
+ Text("Claude Code")
+ .font(.headline)
+ .fontWeight(.bold)
+ Text(entry.session.activeProject)
+ .font(.caption)
+ .foregroundStyle(.secondary)
+ }
+ }
+ Spacer()
+ Text("Active \(relativeDate)")
+ .font(.caption)
+ .foregroundStyle(.secondary)
+ }
+
+ Divider()
+
+ // Stat grid (2x2)
+ LazyVGrid(
+ columns: [GridItem(.flexible()), GridItem(.flexible())],
+ spacing: 8
+ ) {
+ StatTile(
+ icon: "play.circle.fill",
+ iconColor: .purple,
+ value: "\(entry.session.totalSessions)",
+ label: "Total Sessions"
+ )
+ StatTile(
+ icon: "doc.fill",
+ iconColor: .blue,
+ value: "\(entry.session.filesEdited)",
+ label: "Files Edited"
+ )
+ StatTile(
+ icon: "checkmark.seal.fill",
+ iconColor: .green,
+ value: "\(entry.session.tasksCompleted)",
+ label: "Tasks Completed"
+ )
+ StatTile(
+ icon: "pencil.and.outline",
+ iconColor: .orange,
+ value: entry.session.linesChanged.formatted(.number.notation(.compactName)),
+ label: "Lines Changed"
+ )
+ }
+
+ Divider()
+
+ // Model info
+ HStack {
+ Image(systemName: "cpu.fill")
+ .foregroundStyle(.purple)
+ .font(.caption)
+ Text(entry.session.modelUsed)
+ .font(.caption)
+ .foregroundStyle(.secondary)
+ Spacer()
+ Text("Updated \(entry.date, style: .relative) ago")
+ .font(.caption2)
+ .foregroundStyle(.tertiary)
+ }
+ }
+ .padding(16)
+ }
+}
+
+// MARK: - Previews
+
+#Preview(as: .systemSmall) {
+ ClaudeSessionWidget()
+} timeline: {
+ ClaudeSessionEntry.placeholder
+}
+
+#Preview(as: .systemMedium) {
+ ClaudeSessionWidget()
+} timeline: {
+ ClaudeSessionEntry.placeholder
+}
+
+#Preview(as: .systemLarge) {
+ ClaudeSessionWidget()
+} timeline: {
+ ClaudeSessionEntry.placeholder
+}
diff --git a/examples/ios-widget/ClaudeWidget/WidgetExtension/Views/ClaudeStatusWidgetView.swift b/examples/ios-widget/ClaudeWidget/WidgetExtension/Views/ClaudeStatusWidgetView.swift
new file mode 100644
index 0000000000..782b758281
--- /dev/null
+++ b/examples/ios-widget/ClaudeWidget/WidgetExtension/Views/ClaudeStatusWidgetView.swift
@@ -0,0 +1,220 @@
+import SwiftUI
+import WidgetKit
+
+// MARK: - Status Widget
+
+struct ClaudeStatusWidget: Widget {
+ let kind = "ClaudeStatusWidget"
+
+ var body: some WidgetConfiguration {
+ StaticConfiguration(kind: kind, provider: ClaudeStatusProvider()) { entry in
+ ClaudeStatusWidgetView(entry: entry)
+ .containerBackground(.fill.tertiary, for: .widget)
+ }
+ .configurationDisplayName("Claude Status")
+ .description("Shows the current Claude API status and active model.")
+ .supportedFamilies([
+ .systemSmall,
+ .systemMedium,
+ .accessoryCircular,
+ .accessoryRectangular,
+ .accessoryInline
+ ])
+ }
+}
+
+// MARK: - View
+
+struct ClaudeStatusWidgetView: View {
+ @Environment(\.widgetFamily) var family
+ var entry: ClaudeStatusEntry
+
+ var body: some View {
+ switch family {
+ case .systemSmall:
+ SmallStatusView(entry: entry)
+ case .systemMedium:
+ MediumStatusView(entry: entry)
+ case .accessoryCircular:
+ AccessoryCircularStatusView(entry: entry)
+ case .accessoryRectangular:
+ AccessoryRectangularStatusView(entry: entry)
+ case .accessoryInline:
+ AccessoryInlineStatusView(entry: entry)
+ default:
+ SmallStatusView(entry: entry)
+ }
+ }
+}
+
+// MARK: - Small Widget
+
+struct SmallStatusView: View {
+ let entry: ClaudeStatusEntry
+
+ var statusColor: Color {
+ switch entry.status.apiStatus {
+ case .operational: return .green
+ case .degraded: return .yellow
+ case .outage: return .red
+ }
+ }
+
+ var body: some View {
+ VStack(alignment: .leading, spacing: 8) {
+ HStack {
+ Image(systemName: "cpu.fill")
+ .foregroundStyle(.purple)
+ Spacer()
+ Circle()
+ .fill(statusColor)
+ .frame(width: 10, height: 10)
+ }
+
+ Spacer()
+
+ Text("Claude")
+ .font(.headline)
+ .fontWeight(.bold)
+
+ Text(entry.status.apiStatus.rawValue)
+ .font(.caption)
+ .foregroundStyle(statusColor)
+
+ Text(entry.status.currentModel)
+ .font(.caption2)
+ .foregroundStyle(.secondary)
+ .lineLimit(1)
+ .minimumScaleFactor(0.7)
+ }
+ .padding(12)
+ }
+}
+
+// MARK: - Medium Widget
+
+struct MediumStatusView: View {
+ let entry: ClaudeStatusEntry
+
+ var statusColor: Color {
+ switch entry.status.apiStatus {
+ case .operational: return .green
+ case .degraded: return .yellow
+ case .outage: return .red
+ }
+ }
+
+ var body: some View {
+ HStack(spacing: 16) {
+ // Left: Icon + status
+ VStack(alignment: .leading, spacing: 6) {
+ HStack(spacing: 6) {
+ Image(systemName: "cpu.fill")
+ .foregroundStyle(.purple)
+ .font(.title3)
+ Text("Claude Code")
+ .font(.headline)
+ .fontWeight(.bold)
+ }
+
+ HStack(spacing: 4) {
+ Circle()
+ .fill(statusColor)
+ .frame(width: 8, height: 8)
+ Text(entry.status.apiStatus.rawValue)
+ .font(.subheadline)
+ .foregroundStyle(statusColor)
+ }
+
+ Text(entry.status.currentModel)
+ .font(.caption)
+ .foregroundStyle(.secondary)
+ }
+
+ Divider()
+
+ // Right: token info + last updated
+ VStack(alignment: .leading, spacing: 6) {
+ if let tokens = entry.status.remainingTokens {
+ Label {
+ Text("\(tokens.formatted()) tokens")
+ .font(.caption)
+ } icon: {
+ Image(systemName: "bolt.fill")
+ .foregroundStyle(.orange)
+ }
+ }
+
+ Label {
+ Text(entry.status.lastUpdated, style: .relative)
+ .font(.caption)
+ .foregroundStyle(.secondary)
+ } icon: {
+ Image(systemName: "clock")
+ .foregroundStyle(.secondary)
+ }
+ }
+ }
+ .padding(14)
+ }
+}
+
+// MARK: - Accessory Views
+
+struct AccessoryCircularStatusView: View {
+ let entry: ClaudeStatusEntry
+
+ var statusColor: Color {
+ switch entry.status.apiStatus {
+ case .operational: return .green
+ case .degraded: return .yellow
+ case .outage: return .red
+ }
+ }
+
+ var body: some View {
+ ZStack {
+ Circle().fill(statusColor.opacity(0.2))
+ Image(systemName: "cpu.fill")
+ .foregroundStyle(.purple)
+ }
+ }
+}
+
+struct AccessoryRectangularStatusView: View {
+ let entry: ClaudeStatusEntry
+
+ var body: some View {
+ HStack {
+ Image(systemName: "cpu.fill")
+ VStack(alignment: .leading) {
+ Text("Claude API")
+ .font(.headline)
+ Text(entry.status.apiStatus.rawValue)
+ .font(.caption)
+ }
+ }
+ }
+}
+
+struct AccessoryInlineStatusView: View {
+ let entry: ClaudeStatusEntry
+
+ var body: some View {
+ Label(entry.status.apiStatus.rawValue, systemImage: "cpu.fill")
+ }
+}
+
+// MARK: - Preview
+
+#Preview(as: .systemSmall) {
+ ClaudeStatusWidget()
+} timeline: {
+ ClaudeStatusEntry.placeholder
+}
+
+#Preview(as: .systemMedium) {
+ ClaudeStatusWidget()
+} timeline: {
+ ClaudeStatusEntry.placeholder
+}
diff --git a/examples/ios-widget/README.md b/examples/ios-widget/README.md
new file mode 100644
index 0000000000..f7f2a98f48
--- /dev/null
+++ b/examples/ios-widget/README.md
@@ -0,0 +1,96 @@
+# Claude Code iOS 26 Widget
+
+A WidgetKit extension for iOS 26 that shows Claude Code session statistics and API status directly on the Home Screen and Lock Screen.
+
+## Widgets
+
+### Claude Status Widget
+Shows the current Claude API status and active model.
+
+**Supported sizes:** Small, Medium, Accessory Circular, Accessory Rectangular, Accessory Inline
+
+| Small | Medium | Accessory Circular |
+|-------|--------|--------------------|
+| API status + model | Status + token count + last update | Status indicator dot |
+
+### Claude Session Stats Widget
+Shows your Claude Code session statistics (sessions, files edited, tasks completed, lines changed).
+
+**Supported sizes:** Small, Medium, Large
+
+| Small | Medium | Large |
+|-------|--------|-------|
+| Session count + files + tasks | 4-stat grid | Full stats + model info |
+
+## Requirements
+
+- iOS 26+
+- Xcode 16+
+- Swift 6
+- WidgetKit framework
+
+## Project Structure
+
+```
+ClaudeWidget/
+├── ClaudeWidget.xcodeproj/
+│ └── project.pbxproj
+└── WidgetExtension/
+ ├── ClaudeWidgetBundle.swift # Widget entry point (@main)
+ ├── Info.plist
+ ├── Assets.xcassets/
+ ├── Model/
+ │ └── WidgetData.swift # Data models + App Group access
+ ├── Provider/
+ │ ├── ClaudeStatusProvider.swift # Timeline provider (15 min refresh)
+ │ └── ClaudeSessionProvider.swift # Timeline provider (30 min refresh)
+ └── Views/
+ ├── ClaudeStatusWidgetView.swift # Status widget views
+ └── ClaudeSessionWidgetView.swift # Session stats widget views
+```
+
+## Integration
+
+### Writing data from the main app
+
+The widget reads data from a shared **App Group** (`group.com.anthropic.claude-code.widget`).
+Write session data from your main app target:
+
+```swift
+import Foundation
+
+func saveSessionData(_ session: ClaudeSessionData) {
+ guard
+ let encoded = try? JSONEncoder().encode(session),
+ let defaults = UserDefaults(suiteName: "group.com.anthropic.claude-code.widget")
+ else { return }
+ defaults.set(encoded, forKey: "claudeSession")
+ WidgetCenter.shared.reloadTimelines(ofKind: "ClaudeSessionWidget")
+}
+
+func saveStatusData(_ status: ClaudeStatusData) {
+ guard
+ let encoded = try? JSONEncoder().encode(status),
+ let defaults = UserDefaults(suiteName: "group.com.anthropic.claude-code.widget")
+ else { return }
+ defaults.set(encoded, forKey: "claudeStatus")
+ WidgetCenter.shared.reloadTimelines(ofKind: "ClaudeStatusWidget")
+}
+```
+
+### Adding to your Xcode project
+
+1. Open your existing Claude Code app project in Xcode.
+2. **File > New > Target** and choose **Widget Extension**.
+3. Copy the source files from `WidgetExtension/` into the new target.
+4. Enable the **App Groups** capability on both the app target and widget target, using the same group ID (`group.com.anthropic.claude-code.widget`).
+5. Build and run on a device or simulator running iOS 26.
+
+## iOS 26 Features Used
+
+- `containerBackground(.fill.tertiary, for: .widget)` — required on iOS 17+ for widget backgrounds
+- `@Environment(\.widgetFamily)` — adapts layout per widget size
+- `.accessoryCircular` / `.accessoryRectangular` / `.accessoryInline` — Lock Screen widgets (iOS 16+)
+- `.ultraThinMaterial` backgrounds on stat tiles — glass-morphism effect
+- Swift 6 strict concurrency (`Sendable` data models via `Codable`)
+- `#Preview(as:)` macro — live widget previews in Xcode 16