Building native Apple Watch apps
Build native watchOS apps — Swift + SwiftUI + SwiftData, authored in Xcode against the current watchOS 26 stack. There is no other option on this platform: no web wrapper, no cross-platform runtime, no sideloading. A watch app is a native single-target SwiftUI app or it does not exist.
This skill ships a buildable starter (a standalone watch app with a vertical
paging TabView, SwiftData, a Digital Crown dial, and a WidgetKit complication —
built, installed, launched, and unit-tested green on the Apple Watch Series 11
simulator) and 19 focused reference files. Read them on demand — do not dump
them all into context. The map is the decision tree below.
Start here: the mental model
A watch app is not a small iPhone app. It is a glance: the wrist comes up, one fact is read, the wrist drops — seconds, not minutes. Six layers, and the first two have no iOS equivalent:
| Layer | What it is | The modern idiom (watchOS 26) |
|---|---|---|
| The front door | Most sessions never open the app at all. | A WidgetKit accessory widget renders as a watch-face complication and a Smart Stack card. ClockKit is dead (deprecated watchOS 10). An App Intent does the thing without launching anything. |
| App → Scene | The @main struct …: App returns exactly one WindowGroup. |
Single-target apps (WKApplication); WKExtension/WKExtensionDelegateAdaptor are deprecated (watchOS 9.2) and a dual-target build errors. There is no multi-window API at all — openWindow, Window, handlesExternalEvents are simply absent. WKApplicationDelegateAdaptor covers the leftovers; WKNotificationScene covers custom long looks. |
| Navigation | Getting to the right screen in under two seconds. | Vertical-paging TabView(.verticalPage) (watchOS-only) for sibling screens, a deliberately shallow NavigationStack inside each page, or a one-column-at-a-time NavigationSplitView. Depth is the enemy. |
| Input | One thumb, no keyboard. | The Digital Crown is first-class: free scrolling in lists, or an explicit value binding (.focusable() then .digitalCrownRotation). Plus taps, swipeActions, Double Tap (.handGestureShortcut(.primaryAction)), haptics via .sensoryFeedback. Text input is a full-screen dictation/Scribble controller — never require typing. |
| State | Reference-type models the views observe. | The @Observable macro (watchOS 10+) with @State / @Environment / @Bindable — never ObservableObject. Keep the object graph small and partitioned by refresh rate; the watch purges suspended apps silently. |
| Persistence & reach | Where data lives, and how it crosses devices. | SwiftData on the watch's own store. Nothing is shared with the iPhone by default — App Groups do not bridge devices. CloudKit is the cross-device truth; WatchConnectivity is a companion optimization; URLSession is what makes a standalone app work with no phone. |
Four cross-cutting facts shape all watch code:
- The app is usually not running, and "inactive" doesn't mean invisible.
Apple Watch rations CPU harder than any Apple platform. Suspended apps are
purged silently, and watch apps freeze and resume rather than relaunch,
so leaks compound. Meanwhile Always On keeps your dimmed UI on screen for up
to an hour after the wrist drops — the iPhone reflex of tearing down on
.inactiveproduces a frozen watch face. Read\.isLuminanceReduced, throttle the source of updates, and never leave sub-second content on screen. - Health and fitness is the platform's signature domain — and its only real
background exception. A live
HKWorkoutSessionis the one supported way to keep running for hours, keep sensors warm, stay on screen, earn ring credit, and (uniquely) escape the widget reload budget. Everything else — app refresh, extended runtime, audio — is metered in minutes. - Liquid Glass on watchOS is retroactive and cannot be opted out of. Apple
states the new look appears when the user installs watchOS 26 even if you never
rebuild, and
UIDesignRequiresCompatibilitydoes not exist on this platform. So the first action for an existing app is to run it and audit stranded custom styles, not to add glass. On a 162–211 pt screen, budget roughly zero-to-one custom glass element per screen. - There is exactly one door to ship. App Store only — no Developer ID, no notarization gauntlet (unlike the Mac), no sideloading, no alternative marketplaces, and TestFlight (delivered through the paired iPhone) is the sole beta channel. Simpler than macOS, but if App Review says no there is no plan B.
Everything else is detail in the references.
Decision tree → which reference to read
- Project shape (watch-only vs iPhone companion), XcodeGen, the
WK*Info.plist keys, SDK & deployment target, CLI build/install/launch, simulator pairing? →references/project-setup.md, then scaffold with the template (below). - App entry, single-target
WKApplication,WKApplicationDelegateAdaptor, lifecycle & Always On, picking a navigation container, deep links, state restoration? →references/app-structure.md - Views & layout, carousel
List, the vertical-pagingTabView, toolbars, controls, text input, the Digital Crown, gestures, haptics, Water Lock, Now Playing? →references/views-and-interaction.md - Adopting Liquid Glass on custom watch UI (and what restyles for free)?
→
references/liquid-glass.md - State, the
@Observable/Observation framework,@Entry,@AppStorage, keeping the object graph small? →references/state-observation.md - Saving data — SwiftData on the watch, the CloudKit model contract, sharing a store with the complication, migration?
→
references/data-persistence.md - Talking to the iPhone — WatchConnectivity's four transports, reachability, Family Setup, keychain sharing, what is not shared?
→
references/connectivity-and-companion.md - Swift 6 concurrency (
@MainActor,@concurrent, actors, cancellation) and networking — including what compiles but does not work on watchOS? →references/concurrency-and-networking.md - Complications & the Smart Stack — the four accessory families, timelines, reload budget, relevance, push updates, deep links?
→
references/complications-and-widgets.md - App Intents, App Shortcuts & Siri, Control Center controls, the Ultra Action button?
→
references/app-intents-and-controls.md - HealthKit, workout sessions, live metrics, mirroring to iPhone, WorkoutKit, Core Motion, extended runtime?
→
references/health-and-workouts.md - Background execution — app refresh, snapshots, background URLSession, extended runtime sessions, background audio/location, the budget?
→
references/background-and-runtime.md - Notifications — routing between phone and watch, short/long looks, custom SwiftUI notification interfaces, actionable categories?
→
references/notifications.md - A system framework (HealthKit, Core Motion, Core Location, Swift Charts, MapKit, AVFoundation, StoreKit…) — does it even exist on watchOS, and at what floor?
→
references/frameworks.md - Designing it well (watch HIG, glance budget, screen sizes, typography, color on OLED, the circular app icon) plus VoiceOver, hand gestures, Larger Text, localization?
→
references/design-and-accessibility.md - Charging money — StoreKit 2 on the wrist, paywalls on a tiny screen, and the third of the StoreKit SwiftUI layer that is unavailable?
→
references/monetization-storekit.md - Testing (Swift Testing), UI tests on the watch simulator, Previews, on-device debugging, logging, Instruments?
→
references/testing-and-debugging.md - Performance budgets (memory, launch, battery, the 75 MB cap) and shipping — App Store Connect, TestFlight, App Review, screenshots?
→
references/performance-and-shipping.md - What changed across watchOS 9 → 10 → 11 → 26 → 27, which watches run what, or the primary-source citations?
→
references/versions-and-sources.md
When the latest API details matter, verify against Apple's live docs. Apple's
reference pages are JavaScript-rendered — a plain fetch returns only the title.
The reliable primary source is the DocC JSON endpoint:
https://developer.apple.com/tutorials/data/documentation/<path>.json, which
returns full prose, code listings, and the per-platform availability table
(including deprecations). Use that, a rendered scrape (firecrawl), or WebSearch.
The dated version spine lives in references/versions-and-sources.md.
The build workflow
Confirm prerequisites (see end of this file). Building a watch app needs full Xcode 26+ with the watchOS 26 SDK — Command Line Tools alone cannot see the watchOS SDK at all. If
xcode-select -ppoints at CommandLineTools, prefix every build withDEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer(neversudo xcode-select).Scaffold from the template rather than hand-rolling project files:
python3 <skill>/scripts/new_watch_app.py "AppName" \ --bundle-id com.yourco.appname --dest ~/Developer/AppNameThis produces a verified, renamed source tree — a vertical-paging
TabViewwith a SwiftData carousel list, a Digital Crown dial with detents and haptics, an Always-On-aware glance, an@Observableapp model, a WidgetKit complication across all four accessory families, and a Swift Testing suite — and runsxcodegen generateif XcodeGen is installed. It signs ad-hoc, so it builds and runs with no Apple Developer account. Editproject.yml, never the generated.xcodeproj.Generate and test from the command line. These are the exact verified commands (the
-destinationname must matchxcrun simctl list devicesexactly, parenthesised size included —Apple Watch Series 11fails):cd ~/Developer/AppName DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer xcodegen generate DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer \ xcodebuild -project AppName.xcodeproj -scheme AppName \ -destination 'platform=watchOS Simulator,name=Apple Watch Series 11 (46mm)' \ -derivedDataPath /tmp/AppName-DD testBuild features by reading the relevant reference(s) and following their patterns. Keep model state in
@Observableclasses (or SwiftData@Models), inject through the environment, stay on the main actor, and design every screen as one idea readable in two seconds.Verify behavior in the simulator before claiming success. Do not assume the user can read Swift — verify behavior, not just compilation. A green build proves almost nothing here, because the
WK*plist keys are read by the installer, not the compiler. Install, launch, and look:xcrun simctl bootstatus "Apple Watch Series 11 (46mm)" -b xcrun simctl install "Apple Watch Series 11 (46mm)" \ /tmp/AppName-DD/Build/Products/Debug-watchsimulator/AppName.app xcrun simctl launch "Apple Watch Series 11 (46mm)" com.yourco.appname.watchkitapp open -a Simulator # then screenshot each screen and describe what you sawA returned PID means it really launched. Check the small sizes too — SE 3 (40mm) and Ultra 3 (49mm) bracket a 30% width spread, and layout that works on the default Series 11 often breaks on 40mm. What the simulator cannot show you: crown detent haptics, Taptic feedback, Always On dimming, wrist raise, sensors, real networking restrictions, battery, or memory pressure. Say so plainly rather than implying those were verified.
Profile and ship — memory, launch time, battery, and the 75 MB uncompressed cap — then walk the App Store path (privacy manifest, container stub, screenshots, TestFlight) using
references/performance-and-shipping.md.
High-leverage rules (the things that most often go wrong)
The "why" matters more than the rule — understand it and the rest generalizes.
A watch-only app must declare
WKWatchOnly = YES, or it installs nowhere. It compiles perfectly and thensimctl installrejects it outright ("This app does not indicate that it is Watch-only…"). This is a runtime/install failure, not a build error — the single highest-value fact on the platform.WKRunsIndependentlyOfCompanionAppis not a substitute (that's the companion-pair key), whileWKApplicationis synthesized for you byGENERATE_INFOPLIST_FILE— don't write it by hand. Related hard rules:TARGETED_DEVICE_FAMILY = 4, and the watch bundle ID must be its container's ID plus.watchkitapp.Build SDK ≠ deployment target — and watchOS has a second App Store rule. Since April 28, 2026 uploads must be built with Xcode 26+ / the watchOS 26 SDK, and additionally — uniquely to watchOS — since April 2026 watch apps must include 64-bit support. Leave
ARCHSat Standard Architectures (arm64); never hand-pinarmv7k/arm64_32. Keep the deployment target as low as you need and gate new APIs withif #available(watchOS 26, *). Note the numbering jumped 11 → 26: there is no watchOS 12–25, so#available(watchOS 12, *)is always wrong.Single-target only.
WKExtensionandWKExtensionDelegateAdaptorare deprecated (watchOS 9.2) and Xcode errors on a dual-target build. UseWKApplication/WKApplicationDelegate/@WKApplicationDelegateAdaptor. Anything you remember about a "WatchKit Extension" target is dead knowledge..inactivemeans "quiet down", not "you're gone". Always On keeps the frontmost app on screen, dimmed, for ~2 minutes by default and up to an hour. Gate with@Environment(\.isLuminanceReduced), redact with.privacySensitive(), drive updates fromTimelineViewcadence, and remove sub-second content — it stops updating and therefore displays wrong values.The Digital Crown is two different mechanisms.
List,ScrollView, a vertical-pageTabView, and a.wheelPickerscroll under the crown for free — never add.focusable()there. Binding the crown to a value requires.focusable()applied before.digitalCrownRotation; apply it after and rotation silently does nothing. Haptic detents come from theby:stride, not a separate API — turnisHapticFeedbackEnabledoff for genuinely continuous controls.watchOS
Listis a carousel, not an inset-grouped table. Porting iOS list code wholesale loses the platform feel: use.carousel/.elliptical, full-bleed.listRowBackground(RoundedRectangle(…, style: .continuous)), and.containerBackground(_:for: .navigation)instead of.background(). And note Apple's default Dynamic Type is Large on 40/41/42mm and XLarge on 44/45/46/49mm before the user changes anything — any fixed.frame(height:)around text is already broken on most shipping watches. Reach forViewThatFitsandscenePadding().Complications are the app, and they buy you background time. One WidgetKit widget serves the watch face and the Smart Stack (the Smart Stack card is the
accessoryRectangularfamily — give it the layout effort). A complication on the active watch face is literally the price of admission for background app refresh (4 tasks/hour, shared). Widget reloads are budgeted (40–70/day per instance) — with one watch-only superpower: reloads are budget-free while anHKWorkoutSessionis.running.Background is a ladder of rations, and
BGTaskSchedulerisn't on it. There is no BackgroundTasks framework on watchOS 26 — scheduling isWKApplication.scheduleBackgroundRefresh+ SwiftUI.backgroundTask(.appRefresh(id)), with only one pending request at a time, so every handler must re-arm the next one. Above that:WKExtendedRuntimeSession(10 min to 1 hour depending on flavor), background audio, andHKWorkoutSessionas the only hours-long exception. Prefer a widget timeline or a push over paying CPU to stay warm — and always have a widget fallback, because a dated, still-open Apple forum thread reportsscheduleBackgroundRefreshdelivering nothing on watchOS 26.On watchOS, "it compiles" does not mean "it works" — especially in networking. Per Apple DTS (TN3135), the Network framework, BSD sockets,
URLSessionStreamTask, andURLSessionWebSocketTaskdo not work on watchOS outside an audio-streaming session, even though the SDK declares WebSocket available since watchOS 6. The simulator uses the macOS network stack and hides all of it. Use plainURLSessiondata tasks (background sessions for anything real, always settimeoutIntervalForRequest), push, or hand the socket to the paired iPhone. Same discipline for frameworks:Translation,AVCaptureSession, SwiftUIShader, andHKActivityRingViewall ship in the watch SDK marked unavailable — a framework folder proves nothing, so check the availability record.App Groups do NOT bridge the iPhone and the watch. Separate devices, separate containers, even with an identical group identifier — this is the most common architectural error in companion apps. App Groups do work watch-app ⇄ watch-widget (that's how a complication reads real data). Cross-device truth is CloudKit (which does not work in the watch simulator — device-only testing). And reachability is asymmetric: the watch can wake the iOS app with
sendMessage; the phone cannot wake a backgrounded watch app. Design the watch as the requester.WCSessionDelegate+ Swift 6 is a compile-clean runtime crash. Default@MainActorisolation makes your delegate main-actor-isolated, but WCSession delivers every callback on a non-main serial queue — the app builds cleanly and then traps with "Incorrect actor executor assumption." Mark every delegate methodnonisolatedand hop across with anAsyncStream. (Same shape for HealthKit delegates.)Stay on the main actor; on the watch the win is doing less work, not moving it. Swift 6.2 Approachable Concurrency makes UI and models
@MainActorfor free. Offload only measured heavy work (@concurrentfor JSON decoding of WatchConnectivity payloads, workout-sample crunching) — and remember SE-0461: a plainnonisolated async funcruns on the caller's executor and no longer offloads by itself. SetSWIFT_APPROACHABLE_CONCURRENCYandSWIFT_DEFAULT_ACTOR_ISOLATIONon every target, including the complication.Never quote a watchOS memory limit — Apple publishes none. The real constraints are the mechanism (jetsam /
EXC_RESOURCE, with extensions like your complication getting a much lower limit than the app) and the 75 MB uncompressed app-size cap, which is real, applies to every watchOS version, and has no Background Assets escape hatch. Treat any MB figure you encounter as folklore. The behavioral rule is what matters: sync a projection, not the phone's dataset; hold IDs, not object graphs; bound every cache.There is no Apple Intelligence on the wrist yet. The Foundation Models framework is absent from watchOS 26 entirely (as are Vision and Speech), and watchOS 26's Workout Buddy is a system feature with no third-party API. In watchOS 27 (pre-GA) Foundation Models arrives network-only via Private Cloud Compute and never falls back to the paired iPhone. Today's answer: run the model in the companion iOS app and return the result over WatchConnectivity, call a server, or precompute and sync.
Platform status (verify before relying on it — watchOS moves fast)
Research-dated 2026-07-27, verified against the research host (macOS 26.5, Xcode 26.6 (17F113), Swift 6.3.3, watchOS 26.5 SDK):
- watchOS 26 is the current shipping line (GA Sep 15, 2025; numbering
jumped 11 → 26 at WWDC 2025 — there is no watchOS 12–25). Newest public release
26.5 (May 11, 2026); 26.6 is at release candidate (seeded Jul 20,
- — do not call it shipping. watchOS 26 brought Liquid Glass on the wrist, Workout Buddy, the wrist-flick gesture, Control Center controls, user-configurable widgets, WidgetKit push updates, and Swift 6.2 Approachable Concurrency.
- watchOS 27 (WWDC 2026, June 8–12) is a pre-GA developer beta, expected
fall 2026. It announces no app-structure, lifecycle, navigation,
Observation, concurrency, or networking changes — the new surface is Foundation
Models on watch, Vision, HealthKit workout zones, a menopause API, and
SwiftUI reorderable containers. Keep targeting watchOS 26 and
#available-gate anything from 27. - Hardware: the current lineup (announced Sep 9, 2025) is Series 11, SE 3, and Ultra 3 — all on the S10 SiP (there is no "S11 chip"; that is a widely-repeated myth). watchOS 26 runs on Series 6–11, SE 2/3, and Ultra 1/2/3. watchOS 27 drops to only six models (Series 9/10/11, SE 3, Ultra 2/3) — the largest support cut in the product's history.
- Toolchain: Xcode 26.6 is current here and ships the watchOS 26.5 SDK (the SDK lagging Xcode's number is normal, not a broken install). Use Swift 6 language mode. Xcode 27 / Swift 6.4 are betas — never ship an App Store build made with a beta SDK.
- App Store: since April 28, 2026 uploads must use Xcode 26+ / the
watchOS 26 SDK, and since April 2026 watch apps must include 64-bit
support. Required-reason APIs need a
PrivacyInfo.xcprivacymanifest. Display advertising in a watchOS app is a straight rejection (App Review 2.5.18).
references/versions-and-sources.md holds the full timeline, hardware and screen
geometry tables, and the primary-source index. Re-confirm any "latest" claim
against Apple's docs before stating it.
Prerequisites & environment
- macOS 26.x with full Xcode 26+ and the watchOS 26 SDK (device +
simulator). Command Line Tools alone cannot see the watchOS SDK. If
xcode-select -ppoints at CommandLineTools, prefix builds withDEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer— neversudo. - Watch simulators ship inside Xcode. Verify with
xcrun simctl list devicesand use the exact names; the research host has Series 11 (42mm / 46mm), Ultra 3 (49mm), and SE 3 (40mm / 44mm). - Swift 6 language mode with Approachable Concurrency (the template sets
SWIFT_VERSION = 6.0,SWIFT_APPROACHABLE_CONCURRENCY, andSWIFT_DEFAULT_ACTOR_ISOLATION = MainActor). - Optional: XcodeGen (
brew install xcodegen) — the scaffold script uses it to generate the.xcodeprojfromproject.yml. - A paid Apple Developer account ($99/yr) is required to ship (App Store only) and for App Groups, HealthKit background delivery, and CloudKit — but not to build and run locally, since the template signs ad-hoc.
- A real Apple Watch (paired to an iPhone, both in Developer Mode) is the only way to validate haptics, sensors, HealthKit data, Always On, and the real networking restrictions. Debugging tunnels through the paired iPhone over a radio link the watch keeps trying to power down — budget patience.
If a required piece is missing, surface it immediately and offer to help — do not generate code and imply it ran when it could not.
What's in this skill
watchos-dev/
├── SKILL.md (this file — orientation + workflow + map)
├── README.md (human-facing overview & install)
├── references/ (19 focused deep-dives; read on demand — see the decision tree)
├── scripts/new_watch_app.py (scaffold a renamed copy of the template)
└── assets/templates/WatchScaffold/ (verified buildable starter: vertical-paging TabView,
SwiftData carousel list → detail, Digital Crown dial
with detents, Always-On-aware glance, WidgetKit
complication, Swift Testing — built, installed,
launched & green on the Series 11 simulator)