{"id":"33bc1806-df5e-4b32-8e8b-db59e900de8c","shortId":"n4rNCk","kind":"skill","title":"swiftui-patterns","tagline":"Builds SwiftUI views with modern MV architecture, state management, and view composition patterns. Covers @Observable ownership rules, @State/@Bindable/@Environment wiring, view decomposition, custom ViewModifiers, environment values, async data loading with .task, iOS 26+ APIs, ","description":"# SwiftUI Patterns\n\nModern SwiftUI patterns targeting iOS 26+ with Swift 6.3. Covers architecture, state management, view composition, environment wiring, async loading, design polish, and platform/share integration. Navigation and layout patterns live in dedicated sibling skills. Patterns are backward-compatible to iOS 17 unless noted.\n\n## Contents\n\n- [Architecture: Model-View (MV) Pattern](#architecture-model-view-mv-pattern)\n- [State Management](#state-management)\n- [View Ordering Convention](#view-ordering-convention)\n- [View Composition](#view-composition)\n- [Environment](#environment)\n- [Async Data Loading](#async-data-loading)\n- [iOS 26+ New APIs](#ios-26-new-apis)\n- [Performance Guidelines](#performance-guidelines)\n- [HIG Alignment](#hig-alignment)\n- [Writing Tools (iOS 18+)](#writing-tools-ios-18)\n- [Common Mistakes](#common-mistakes)\n- [Review Checklist](#review-checklist)\n- [References](#references)\n\n**Scope boundary:** This skill covers architecture, state ownership, composition, environment wiring, async loading, and related SwiftUI app structure patterns. Detailed navigation patterns are covered in the `swiftui-navigation` skill, including `NavigationStack`, `NavigationSplitView`, sheets, tabs, and deep-linking patterns. Detailed layout, container, and component patterns are covered in the `swiftui-layout-components` skill, including stacks, grids, lists, scroll view patterns, forms, controls, search UI with `.searchable`, overlays, and related layout components.\n\n## Architecture: Model-View (MV) Pattern\n\nDefault to MV -- views are lightweight state expressions; models and services own business logic. Do not introduce view models unless the existing code already uses them.\n\n**Core principles:**\n- Favor `@State`, `@Environment`, `@Query`, `.task`, and `.onChange` for orchestration\n- Inject services and shared models via `@Environment`; keep views small and composable\n- Split large views into smaller subviews rather than introducing a view model\n- Test models, services, and business logic; keep views simple and declarative\n\n```swift\nstruct FeedView: View {\n    @Environment(FeedClient.self) private var client\n\n    enum ViewState {\n        case loading, error(String), loaded([Post])\n    }\n\n    @State private var viewState: ViewState = .loading\n\n    var body: some View {\n        List {\n            switch viewState {\n            case .loading:\n                ProgressView()\n            case .error(let message):\n                ContentUnavailableView(\"Error\", systemImage: \"exclamationmark.triangle\",\n                                       description: Text(message))\n            case .loaded(let posts):\n                ForEach(posts) { post in\n                    PostRow(post: post)\n                }\n            }\n        }\n        .task { await loadFeed() }\n        .refreshable { await loadFeed() }\n    }\n\n    private func loadFeed() async {\n        do {\n            let posts = try await client.getFeed()\n            viewState = .loaded(posts)\n        } catch {\n            viewState = .error(error.localizedDescription)\n        }\n    }\n}\n```\n\nFor MV pattern rationale, app wiring, and lightweight client examples, see [references/architecture-patterns.md](references/architecture-patterns.md).\n\n## State Management\n\n### @Observable Ownership Rules\n\n**Important:** Always annotate `@Observable` view model classes with `@MainActor` to ensure UI-bound state is updated on the main thread. Required for Swift 6 concurrency safety.\n\n| Wrapper | When to Use |\n|---------|-------------|\n| `@State` | View owns the object or value. Creates and manages lifecycle. |\n| `let` | View receives an `@Observable` object. Read-only observation -- no wrapper needed. |\n| `@Bindable` | View receives an `@Observable` object and needs two-way bindings (`$property`). |\n| `@Environment(Type.self)` | Access shared `@Observable` object from environment. |\n| `@State` (value types) | View-local simple state: toggles, counters, text field values. Always `private`. |\n| `@Binding` | Two-way connection to parent's `@State` or `@Bindable` property. |\n\n### Ownership Pattern\n\n```swift\n// @Observable view model -- always @MainActor\n@MainActor\n@Observable final class ItemStore {\n    var title = \"\"\n    var items: [Item] = []\n}\n\n// View that OWNS the model\nstruct ParentView: View {\n    @State var viewModel = ItemStore()\n\n    var body: some View {\n        ChildView(store: viewModel)\n            .environment(viewModel)\n    }\n}\n\n// View that READS (no wrapper needed for @Observable)\nstruct ChildView: View {\n    let store: ItemStore\n\n    var body: some View { Text(store.title) }\n}\n\n// View that BINDS (needs two-way access)\nstruct EditView: View {\n    @Bindable var store: ItemStore\n\n    var body: some View {\n        TextField(\"Title\", text: $store.title)\n    }\n}\n\n// View that reads from ENVIRONMENT\nstruct DeepView: View {\n    @Environment(ItemStore.self) var store\n\n    var body: some View {\n        @Bindable var s = store\n        TextField(\"Title\", text: $s.title)\n    }\n}\n```\n\n**Granular tracking:** SwiftUI only re-renders views that read properties that changed. If a view reads `items` but not `isLoading`, changing `isLoading` does not trigger a re-render. This is a major performance advantage over `ObservableObject`.\n\n### Legacy ObservableObject\n\nOnly use if supporting iOS 16 or earlier. `@StateObject` → `@State`, `@ObservedObject` → `let`, `@EnvironmentObject` → `@Environment(Type.self)`.\n\n## View Ordering Convention\n\nOrder members top to bottom: 1) `@Environment` 2) `let` properties 3) `@State` / stored properties 4) computed `var` 5) `init` 6) `body` 7) view builders / helpers 8) async functions\n\n## View Composition\n\n### Extract Subviews\n\nBreak views into focused subviews. Each should have a single responsibility.\n\n```swift\nvar body: some View {\n    VStack {\n        HeaderSection(title: title, isPinned: isPinned)\n        DetailsSection(details: details)\n        ActionsSection(onSave: onSave, onCancel: onCancel)\n    }\n}\n```\n\n### Computed View Properties\n\nKeep related subviews as computed properties in the same file; extract to a standalone `View` struct when reuse is intended or the subview carries its own state.\n\n```swift\nvar body: some View {\n    List {\n        header\n        filters\n        results\n    }\n}\n\nprivate var header: some View {\n    VStack(alignment: .leading) {\n        Text(title).font(.title2)\n        Text(subtitle).font(.subheadline)\n    }\n}\n```\n\n### ViewBuilder Functions\n\nFor conditional logic that does not warrant a separate struct:\n\n```swift\n@ViewBuilder\nprivate func statusBadge(for status: Status) -> some View {\n    switch status {\n    case .active: Text(\"Active\").foregroundStyle(.green)\n    case .inactive: Text(\"Inactive\").foregroundStyle(.secondary)\n    }\n}\n```\n\n### Custom View Modifiers\n\nExtract repeated styling into `ViewModifier`:\n\n```swift\nstruct CardStyle: ViewModifier {\n    func body(content: Content) -> some View {\n        content\n            .padding()\n            .background(.background)\n            .clipShape(.rect(cornerRadius: 12))\n            .shadow(radius: 2)\n    }\n}\nextension View { func cardStyle() -> some View { modifier(CardStyle()) } }\n```\n\n### Stable View Tree\n\nAvoid top-level conditional view swapping. Prefer a single stable base view with conditions inside sections or modifiers. When a view file exceeds ~300 lines, split with extensions and `// MARK: -` comments.\n\n## Environment\n\n### Custom Environment Values\n\nUse `@Entry` for custom environment values and actions. It generates the entry boilerplate for `EnvironmentValues`.\n\n```swift\nextension EnvironmentValues {\n    @Entry var theme: Theme = .default\n    @Entry var refreshFeed: @Sendable () async -> Void = {}\n}\n\n// Usage\n.environment(\\.theme, customTheme)\n.environment(\\.refreshFeed) { await feedStore.refresh() }\n\n@Environment(\\.theme) private var theme\n@Environment(\\.refreshFeed) private var refreshFeed\n```\n\nFor iOS 17-compatible code or older compatibility shims, use manual `EnvironmentKey` types instead.\n\n### Common Built-in Environment Values\n\n```swift\n@Environment(\\.dismiss) var dismiss\n@Environment(\\.colorScheme) var colorScheme\n@Environment(\\.dynamicTypeSize) var dynamicTypeSize\n@Environment(\\.horizontalSizeClass) var sizeClass\n@Environment(\\.isSearching) var isSearching\n@Environment(\\.openURL) var openURL\n@Environment(\\.modelContext) var modelContext\n```\n\n## Async Data Loading\n\nAlways use `.task` -- it cancels automatically on view disappear:\n\n```swift\nstruct ItemListView: View {\n    @State var store = ItemStore()\n\n    var body: some View {\n        List(store.items) { item in\n            ItemRow(item: item)\n        }\n        .task { await store.load() }\n        .refreshable { await store.refresh() }\n    }\n}\n```\n\nUse `.task(id:)` to re-run when a dependency changes:\n\n```swift\n.task(id: searchText) {\n    guard !searchText.isEmpty else { return }\n    await search(query: searchText)\n}\n```\n\nNever create manual `Task` in `onAppear` unless you need to store a reference for cancellation. Exception: `Task {}` is acceptable in synchronous action closures (e.g., Button actions) for immediate state updates before async work.\n\n## iOS 26+ New APIs\n\n- **`.scrollEdgeEffectStyle(.soft, for: .top)`** -- fading edge effect on scroll edges\n- **`.backgroundExtensionEffect()`** -- mirror/blur at safe area edges\n- **`@Animatable`** macro -- synthesizes `AnimatableData` conformance automatically (see `swiftui-animation` skill)\n- **`TextEditor`** -- now accepts `AttributedString` for rich text\n\n## Performance Guidelines\n\n- **Lazy stacks/grids:** Use `LazyVStack`, `LazyHStack`, `LazyVGrid`, `LazyHGrid` for large collections. Regular stacks render all children immediately.\n- **Stable IDs:** All items in `List`/`ForEach` must conform to `Identifiable` with stable IDs. Never use array indices.\n- **Avoid body recomputation:** Move filtering and sorting to computed properties or the model, not inline in `body`.\n- **Equatable views:** For complex views that re-render unnecessarily, conform to `Equatable`.\n\n## HIG Alignment\n\nFollow Apple Human Interface Guidelines for layout, typography, color, and accessibility. Key rules:\n\n- Use semantic colors (`Color.primary`, `.secondary`, `Color(uiColor: .systemBackground)`) for automatic light/dark mode\n- Use system font styles (`.title`, `.headline`, `.body`, `.caption`) for Dynamic Type support\n- Use `ContentUnavailableView` for empty and error states\n- Omit `spacing:` on stacks unless a specific value is required — `nil` (the default) uses platform-appropriate adaptive spacing\n- Support adaptive layouts via `horizontalSizeClass`\n- Provide VoiceOver labels (`.accessibilityLabel`) and support Dynamic Type accessibility sizes by switching layout orientation\n\nSee [references/design-polish.md](references/design-polish.md) for HIG, theming, haptics, focus, transitions, and loading patterns.\n\n## Writing Tools (iOS 18+)\n\nControl the Apple Intelligence Writing Tools experience on text views with `.writingToolsBehavior(_:)`.\n\n| Level | Effect | When to use |\n|-------|--------|-------------|\n| `.complete` | Full inline rewriting (proofread, rewrite, transform) | Notes, email, documents |\n| `.limited` | Reduced overlay-panel experience | Code editors, validated forms |\n| `.disabled` | Writing Tools hidden entirely | Passwords, search bars |\n| `.automatic` | System chooses based on context (default) | Most views |\n\n```swift\nTextEditor(text: $body)\n    .writingToolsBehavior(.complete)\nTextField(\"Search…\", text: $query)\n    .writingToolsBehavior(.disabled)\n```\n\n**Detecting active sessions:** Read `isWritingToolsActive` on `UITextView` (UIKit) to defer validation or suspend undo grouping until a rewrite finishes.\n\n> **Docs:** [WritingToolsBehavior](https://sosumi.ai/documentation/swiftui/writingtoolsbehavior) · [writingToolsBehavior(_:)](https://sosumi.ai/documentation/swiftui/view/writingtoolsbehavior(_:))\n\n## Common Mistakes\n\n1. Using `@ObservedObject` to create objects -- use `@StateObject` (legacy) or `@State` (modern)\n2. Heavy computation in view `body` -- move to model or computed property\n3. Not using `.task` for async work -- manual `Task` in `onAppear` leaks if not cancelled\n4. Array indices as `ForEach` IDs -- causes incorrect diffing and UI bugs\n5. Forgetting `@Bindable` -- `$property` syntax on `@Observable` requires `@Bindable`\n6. Over-using `@State` -- only for view-local state; shared state belongs in `@Observable`\n7. Not extracting subviews -- long body blocks are hard to read and optimize\n8. Using `NavigationView` -- deprecated; use `NavigationStack`\n9. Reaching for `foregroundColor(_:)` when `foregroundStyle(_:)` better matches semantic styling\n10. Inline closures in body -- extract complex closures to methods\n11. `.sheet(isPresented:)` when state represents a model -- use `.sheet(item:)` instead\n12. **Using `AnyView` for type erasure** -- causes identity resets and disables diffing. Use `@ViewBuilder`, `Group`, or generics instead. See [references/deprecated-migration.md](references/deprecated-migration.md)\n13. **Putting `@AppStorage` inside an `@Observable` class** -- `@AppStorage` is a SwiftUI `DynamicProperty`; it only triggers view updates when used directly in a `View`. Inside an `@Observable` class, observation tracking never sees the change. Keep `@AppStorage` in views, or read/write `UserDefaults` directly inside the `@Observable` class:\n\n```swift\n// Wrong -- @AppStorage is invisible to @Observable tracking\n@MainActor @Observable final class Settings {\n    @AppStorage(\"theme\") var theme: String = \"system\" // view won't update\n}\n\n// Right -- UserDefaults read/write with a normal stored property\n@MainActor @Observable final class Settings {\n    var theme: String {\n        didSet { UserDefaults.standard.set(theme, forKey: \"theme\") }\n    }\n\n    init() {\n        theme = UserDefaults.standard.string(forKey: \"theme\") ?? \"system\"\n    }\n}\n```\n\n14. Hard-coding `spacing:` on every stack -- omit it to get adaptive platform spacing; only specify when the value is intentional\n\n## Review Checklist\n\n- [ ] `@Observable` used for shared state models (not `ObservableObject` on iOS 17+)\n- [ ] `@State` owns objects; `let`/`@Bindable` receives them\n- [ ] `NavigationStack` used (not `NavigationView`)\n- [ ] `.task` modifier for async data loading\n- [ ] `LazyVStack`/`LazyHStack` for large collections\n- [ ] Stable `Identifiable` IDs (not array indices)\n- [ ] Views decomposed into focused subviews\n- [ ] No heavy computation in view `body`\n- [ ] Environment used for deeply shared state\n- [ ] `foregroundStyle(_:)` used when semantic styling is preferable to a fixed color\n- [ ] Custom `ViewModifier` for repeated styling\n- [ ] `.sheet(item:)` preferred over `.sheet(isPresented:)`\n- [ ] Sheets own their actions and call `dismiss()` internally\n- [ ] MV pattern followed -- no unnecessary view models\n- [ ] `@Observable` view model classes are `@MainActor`-isolated\n- [ ] Model types passed across concurrency boundaries are `Sendable`\n- [ ] Stack `spacing:` omitted unless a specific value is required (prefer adaptive default)\n\n## References\n\n- Architecture, app wiring, and lightweight clients: [references/architecture-patterns.md](references/architecture-patterns.md)\n- Design polish (HIG, theming, haptics, transitions, loading, focus): [references/design-polish.md](references/design-polish.md)\n- Deprecated API migration: [references/deprecated-migration.md](references/deprecated-migration.md)\n- Platform and sharing patterns (Transferable, media, menus, macOS settings): [references/platform-and-sharing.md](references/platform-and-sharing.md)","tags":["swiftui","patterns","swift","ios","skills","dpearson2699","accessibility","agent-skills","ai-coding","apple","claude-code","codex-skills"],"capabilities":["skill","source-dpearson2699","skill-swiftui-patterns","topic-accessibility","topic-agent-skills","topic-ai-coding","topic-apple","topic-claude-code","topic-codex-skills","topic-cursor-skills","topic-ios","topic-ios-development","topic-liquid-glass","topic-localization","topic-mapkit"],"categories":["swift-ios-skills"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/dpearson2699/swift-ios-skills/swiftui-patterns","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"cli":"npx skills add dpearson2699/swift-ios-skills","source_repo":"https://github.com/dpearson2699/swift-ios-skills","install_from":"skills.sh"}},"qualityScore":"0.700","qualityRationale":"deterministic score 0.70 from registry signals: · indexed on github topic:agent-skills · 599 github stars · SKILL.md body (15,582 chars)","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill-github:v1","enrichmentVersion":1,"enrichedAt":"2026-05-18T18:53:45.950Z","embedding":null,"createdAt":"2026-04-18T22:01:26.725Z","updatedAt":"2026-05-18T18:53:45.950Z","lastSeenAt":"2026-05-18T18:53:45.950Z","tsv":"'-26':128 '/documentation/swiftui/view/writingtoolsbehavior(_:))':1392 '/documentation/swiftui/writingtoolsbehavior)':1388 '1':682,1395 '10':1500 '11':1510 '12':855,1522 '13':1543 '14':1638 '16':664 '17':81,955,1672 '18':145,150,1298 '2':684,858,1407 '26':37,46,124,1096 '3':687,1419 '300':894 '4':691,1434 '5':694,1446 '6':434,696,1455 '6.3':49 '7':698,1471 '8':702,1484 '9':1490 'accept':1080,1128 'access':480,579,1211,1277 'accessibilitylabel':1272 'across':1765 'action':913,1083,1087,1743 'actionssect':734 'activ':819,821,1366 'adapt':1262,1265,1650,1780 'advantag':654 'align':138,141,784,1200 'alreadi':265 'alway':411,499,519,1005 'anim':1124 'animat':1115 'animatabledata':1118 'annot':412 'anyview':1524 'api':38,126,131,1098,1802 'app':179,396,1784 'appl':1202,1301 'appropri':1261 'appstorag':1545,1550,1577,1590,1601 'architectur':10,51,85,92,168,236,1783 'architecture-model-view-mv-pattern':91 'area':1113 'array':1167,1435,1699 'async':31,58,116,120,174,378,703,933,1002,1093,1424,1687 'async-data-load':119 'attributedstr':1129 'automat':1010,1120,1223,1344 'avoid':870,1169 'await':370,373,383,941,1034,1037,1058 'background':850,851 'backgroundextensioneffect':1109 'backward':77 'backward-compat':76 'bar':1343 'base':881,1347 'belong':1468 'better':1496 'bind':476,501,574 'bindabl':22,465,511,583,611,1448,1454,1677 'block':1477 'bodi':338,544,567,588,608,697,722,771,843,1023,1170,1185,1232,1356,1412,1476,1504,1711 'boilerpl':918 'bottom':681 'bound':423 'boundari':164,1767 'break':709 'bug':1445 'build':4 'builder':700 'built':969 'built-in':968 'busi':254,307 'button':1086 'call':1745 'cancel':1009,1076,1433 'caption':1233 'cardstyl':840,862,866 'carri':765 'case':325,344,347,358,818,824 'catch':388 'caus':1440,1528 'chang':631,640,1049,1575 'checklist':157,160,1661 'children':1149 'childview':547,561 'choos':1346 'class':416,524,1549,1569,1587,1599,1622,1758 'client':322,400,1788 'client.getfeed':384 'clipshap':852 'closur':1084,1502,1507 'code':264,957,1332,1641 'collect':1144,1694 'color':1209,1216,1219,1728 'color.primary':1217 'colorschem':979,981 'comment':901 'common':151,154,967,1393 'common-mistak':153 'compat':78,956,960 'complet':1316,1358 'complex':1189,1506 'compon':207,216,235 'compos':290 'composit':15,55,110,113,171,706 'comput':692,739,746,1177,1409,1417,1708 'concurr':435,1766 'condit':797,874,884 'conform':1119,1159,1196 'connect':505 'contain':205 'content':84,844,845,848 'contentunavailableview':351,1239 'context':1349 'control':226,1299 'convent':104,108,676 'core':268 'cornerradius':854 'counter':495 'cover':17,50,167,186,210 'creat':448,1063,1399 'custom':27,830,903,909,1729 'customthem':938 'data':32,117,121,1003,1688 'declar':313 'decompos':1702 'decomposit':26 'dedic':71 'deep':200 'deep-link':199 'deepli':1715 'deepview':601 'default':242,928,1257,1350,1781 'defer':1374 'depend':1048 'deprec':1487,1801 'descript':355 'design':60,1791 'detail':182,203,732,733 'detailssect':731 'detect':1365 'didset':1627 'dif':1442,1533 'direct':1562,1583 'disabl':1336,1364,1532 'disappear':1013 'dismiss':975,977,1746 'doc':1384 'document':1325 'dynam':1235,1275 'dynamicproperti':1554 'dynamictypes':983,985 'e.g':1085 'earlier':666 'edg':1104,1108,1114 'editor':1333 'editview':581 'effect':1105,1312 'els':1056 'email':1324 'empti':1241 'ensur':420 'entir':1340 'entri':907,917,924,929 'enum':323 'environ':23,29,56,114,115,172,272,285,318,478,485,550,599,603,672,683,902,904,910,936,939,943,948,971,974,978,982,986,990,994,998,1712 'environmentkey':964 'environmentobject':671 'environmentvalu':920,923 'equat':1186,1198 'erasur':1527 'error':327,348,352,390,1243 'error.localizeddescription':391 'everi':1644 'exampl':401 'exceed':893 'except':1077 'exclamationmark.triangle':354 'exist':263 'experi':1305,1331 'express':249 'extens':859,898,922 'extract':707,752,833,1473,1505 'fade':1103 'favor':270 'feedclient.self':319 'feedstore.refresh':942 'feedview':316 'field':497 'file':751,892 'filter':776,1173 'final':523,1598,1621 'finish':1383 'fix':1727 'focus':712,1290,1704,1798 'follow':1201,1750 'font':788,792,1228 'foreach':362,1157,1438 'foregroundcolor':1493 'foregroundstyl':822,828,1495,1718 'forget':1447 'forkey':1630,1635 'form':225,1335 'full':1317 'func':376,809,842,861 'function':704,795 'generat':915 'generic':1538 'get':1649 'granular':619 'green':823 'grid':220 'group':1379,1536 'guard':1054 'guidelin':133,136,1134,1205 'haptic':1289,1795 'hard':1479,1640 'hard-cod':1639 'header':775,780 'headersect':726 'headlin':1231 'heavi':1408,1707 'helper':701 'hidden':1339 'hig':137,140,1199,1287,1793 'hig-align':139 'horizontalsizeclass':987,1268 'human':1203 'id':1041,1052,1152,1164,1439,1697 'ident':1529 'identifi':1161,1696 'immedi':1089,1150 'import':410 'inact':825,827 'includ':193,218 'incorrect':1441 'indic':1168,1436,1700 'init':695,1632 'inject':279 'inlin':1183,1318,1501 'insid':885,1546,1566,1584 'instead':966,1521,1539 'integr':64 'intellig':1302 'intend':761 'intent':1659 'interfac':1204 'intern':1747 'introduc':258,299 'invis':1592 'io':36,45,80,123,127,144,149,663,954,1095,1297,1671 'isload':639,641 'isol':1761 'ispin':729,730 'ispres':1512,1739 'issearch':991,993 'iswritingtoolsact':1369 'item':529,530,636,1028,1031,1032,1154,1520,1735 'itemlistview':1016 'itemrow':1030 'itemstor':525,542,565,586,1021 'itemstore.self':604 'keep':286,309,742,1576 'key':1212 'label':1271 'larg':292,1143,1693 'layout':67,204,215,234,1207,1266,1281 'lazi':1135 'lazyhgrid':1141 'lazyhstack':1139,1691 'lazyvgrid':1140 'lazyvstack':1138,1690 'lead':785 'leak':1430 'legaci':657,1403 'let':349,360,380,452,563,670,685,1676 'level':873,1311 'lifecycl':451 'light/dark':1224 'lightweight':247,399,1787 'limit':1326 'line':895 'link':201 'list':221,341,774,1026,1156 'live':69 'load':33,59,118,122,175,326,329,336,345,359,386,1004,1293,1689,1797 'loadfe':371,374,377 'local':491,1464 'logic':255,308,798 'long':1475 'maco':1813 'macro':1116 'main':429 'mainactor':418,520,521,1596,1619,1760 'major':652 'manag':12,53,98,101,406,450 'manual':963,1064,1426 'mark':900 'match':1497 'media':1811 'member':678 'menus':1812 'messag':350,357 'method':1509 'migrat':1803 'mirror/blur':1110 'mistak':152,155,1394 'mode':1225 'model':87,93,238,250,260,283,302,304,415,518,535,1181,1415,1517,1667,1754,1757,1762 'model-view':86,237 'modelcontext':999,1001 'modern':8,41,1406 'modifi':832,865,888,1685 'move':1172,1413 'must':1158 'mv':9,89,95,240,244,393,1748 'navig':65,183,191 'navigationsplitview':195 'navigationstack':194,1489,1680 'navigationview':1486,1683 'need':464,472,557,575,1070 'never':1062,1165,1572 'new':125,130,1097 'new-api':129 'nil':1255 'normal':1616 'note':83,1323 'object':445,457,470,483,1400,1675 'observ':18,407,413,456,461,469,482,516,522,559,1452,1470,1548,1568,1570,1586,1594,1597,1620,1662,1755 'observableobject':656,658,1669 'observedobject':669,1397 'older':959 'omit':1245,1646,1772 'onappear':1067,1429 'oncancel':737,738 'onchang':276 'onsav':735,736 'openurl':995,997 'optim':1483 'orchestr':278 'order':103,107,675,677 'orient':1282 'over-us':1456 'overlay':231,1329 'overlay-panel':1328 'own':443,533,1674 'ownership':19,170,408,513 'pad':849 'panel':1330 'parent':507 'parentview':537 'pass':1764 'password':1341 'pattern':3,16,40,43,68,74,90,96,181,184,202,208,224,241,394,514,1294,1749,1809 'perform':132,135,653,1133 'performance-guidelin':134 'platform':1260,1651,1806 'platform-appropri':1259 'platform/share':63 'polish':61,1792 'post':330,361,363,364,367,368,381,387 'postrow':366 'prefer':877,1724,1736,1779 'principl':269 'privat':320,332,375,500,778,808,945,950 'progressview':346 'proofread':1320 'properti':477,512,629,686,690,741,747,1178,1418,1449,1618 'provid':1269 'put':1544 'queri':273,1060,1362 'radius':857 'rather':297 'rational':395 're':624,647,1044,1193 're-rend':623,646,1192 're-run':1043 'reach':1491 'read':459,554,597,628,635,1368,1481 'read-on':458 'read/write':1581,1613 'receiv':454,467,1678 'recomput':1171 'rect':853 'reduc':1327 'refer':161,162,1074,1782 'references/architecture-patterns.md':403,404,1789,1790 'references/deprecated-migration.md':1541,1542,1804,1805 'references/design-polish.md':1284,1285,1799,1800 'references/platform-and-sharing.md':1815,1816 'refresh':372,1036 'refreshfe':931,940,949,952 'regular':1145 'relat':177,233,743 'render':625,648,1147,1194 'repeat':834,1732 'repres':1515 'requir':431,1254,1453,1778 'reset':1530 'respons':719 'result':777 'return':1057 'reus':759 'review':156,159,1660 'review-checklist':158 'rewrit':1319,1321,1382 'rich':1131 'right':1611 'rule':20,409,1213 'run':1045 's.title':618 'safe':1112 'safeti':436 'scope':163 'scroll':222,1107 'scrolledgeeffectstyl':1099 'search':227,1059,1342,1360 'searchabl':230 'searchtext':1053,1061 'searchtext.isempty':1055 'secondari':829,1218 'section':886 'see':402,1121,1283,1540,1573 'semant':1215,1498,1721 'sendabl':932,1769 'separ':804 'servic':252,280,305 'session':1367 'set':1600,1623,1814 'shadow':856 'share':282,481,1466,1665,1716,1808 'sheet':196,1511,1519,1734,1738,1740 'shim':961 'sibl':72 'simpl':311,492 'singl':718,879 'size':1278 'sizeclass':989 'skill':73,166,192,217,1125 'skill-swiftui-patterns' 'small':288 'smaller':295 'soft':1100 'sort':1175 'sosumi.ai':1387,1391 'sosumi.ai/documentation/swiftui/view/writingtoolsbehavior(_:))':1390 'sosumi.ai/documentation/swiftui/writingtoolsbehavior)':1386 'source-dpearson2699' 'space':1246,1263,1642,1652,1771 'specif':1251,1775 'specifi':1654 'split':291,896 'stabl':867,880,1151,1163,1695 'stack':219,1146,1248,1645,1770 'stacks/grids':1136 'standalon':755 'state':11,21,52,97,100,169,248,271,331,405,424,441,486,493,509,539,668,688,768,1018,1090,1244,1405,1459,1465,1467,1514,1666,1673,1717 'state-manag':99 'stateobject':667,1402 'status':812,813,817 'statusbadg':810 'store':548,564,585,606,614,689,1020,1072,1617 'store.items':1027 'store.load':1035 'store.refresh':1038 'store.title':571,594 'string':328,1605,1626 'struct':315,536,560,580,600,757,805,839,1015 'structur':180 'style':835,1229,1499,1722,1733 'subheadlin':793 'subtitl':791 'subview':296,708,713,744,764,1474,1705 'support':662,1237,1264,1274 'suspend':1377 'swap':876 'swift':48,314,433,515,720,769,806,838,921,973,1014,1050,1353,1588 'swiftui':2,5,39,42,178,190,214,621,1123,1553 'swiftui-anim':1122 'swiftui-layout-compon':213 'swiftui-navig':189 'swiftui-pattern':1 'switch':342,816,1280 'synchron':1082 'syntax':1450 'synthes':1117 'system':1227,1345,1606,1637 'systembackground':1221 'systemimag':353 'tab':197 'target':44 'task':35,274,369,1007,1033,1040,1051,1065,1078,1422,1427,1684 'test':303 'text':356,496,570,593,617,786,790,820,826,1132,1307,1355,1361 'texteditor':1126,1354 'textfield':591,615,1359 'theme':926,927,937,944,947,1288,1602,1604,1625,1629,1631,1633,1636,1794 'thread':430 'titl':527,592,616,727,728,787,1230 'title2':789 'toggl':494 'tool':143,148,1296,1304,1338 'top':679,872,1102 'top-level':871 'topic-accessibility' 'topic-agent-skills' 'topic-ai-coding' 'topic-apple' 'topic-claude-code' 'topic-codex-skills' 'topic-cursor-skills' 'topic-ios' 'topic-ios-development' 'topic-liquid-glass' 'topic-localization' 'topic-mapkit' 'track':620,1571,1595 'transfer':1810 'transform':1322 'transit':1291,1796 'tree':869 'tri':382 'trigger':644,1557 'two':474,503,577 'two-way':473,502,576 'type':488,965,1236,1276,1526,1763 'type.self':479,673 'typographi':1208 'ui':228,422,1444 'ui-bound':421 'uicolor':1220 'uikit':1372 'uitextview':1371 'undo':1378 'unless':82,261,1068,1249,1773 'unnecessari':1752 'unnecessarili':1195 'updat':426,1091,1559,1610 'usag':935 'use':266,440,660,906,962,1006,1039,1137,1166,1214,1226,1238,1258,1315,1396,1401,1421,1458,1485,1488,1518,1523,1534,1561,1663,1681,1713,1719 'userdefault':1582,1612 'userdefaults.standard.set':1628 'userdefaults.standard.string':1634 'valid':1334,1375 'valu':30,447,487,498,905,911,972,1252,1657,1776 'var':321,333,337,526,528,540,543,566,584,587,605,607,612,693,721,770,779,925,930,946,951,976,980,984,988,992,996,1000,1019,1022,1603,1624 'via':284,1267 'view':6,14,25,54,88,94,102,106,109,112,223,239,245,259,287,293,301,310,317,340,414,442,453,466,490,517,531,538,546,552,562,569,572,582,590,595,602,610,626,634,674,699,705,710,724,740,756,773,782,815,831,847,860,864,868,875,882,891,1012,1017,1025,1187,1190,1308,1352,1411,1463,1558,1565,1579,1607,1701,1710,1753,1756 'view-composit':111 'view-loc':489,1462 'view-ordering-convent':105 'viewbuild':794,807,1535 'viewmodel':541,549,551 'viewmodifi':28,837,841,1730 'viewstat':324,334,335,343,385,389 'voiceov':1270 'void':934 'vstack':725,783 'warrant':802 'way':475,504,578 'wire':24,57,173,397,1785 'won':1608 'work':1094,1425 'wrapper':437,463,556 'write':142,147,1295,1303,1337 'writing-tools-io':146 'writingtoolsbehavior':1310,1357,1363,1385,1389 'wrong':1589","prices":[{"id":"d533a673-be24-451d-9dfb-dc01d77d4e79","listingId":"33bc1806-df5e-4b32-8e8b-db59e900de8c","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"dpearson2699","category":"swift-ios-skills","install_from":"skills.sh"},"createdAt":"2026-04-18T22:01:26.725Z"}],"sources":[{"listingId":"33bc1806-df5e-4b32-8e8b-db59e900de8c","source":"github","sourceId":"dpearson2699/swift-ios-skills/swiftui-patterns","sourceUrl":"https://github.com/dpearson2699/swift-ios-skills/tree/main/skills/swiftui-patterns","isPrimary":false,"firstSeenAt":"2026-04-18T22:01:26.725Z","lastSeenAt":"2026-05-18T18:53:45.950Z"},{"listingId":"33bc1806-df5e-4b32-8e8b-db59e900de8c","source":"skills_sh","sourceId":"dpearson2699/swift-ios-skills/swiftui-patterns","sourceUrl":"https://skills.sh/dpearson2699/swift-ios-skills/swiftui-patterns","isPrimary":true,"firstSeenAt":"2026-05-07T20:40:44.251Z","lastSeenAt":"2026-05-07T22:40:31.452Z"}],"details":{"listingId":"33bc1806-df5e-4b32-8e8b-db59e900de8c","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"dpearson2699","slug":"swiftui-patterns","github":{"repo":"dpearson2699/swift-ios-skills","stars":599,"topics":["accessibility","agent-skills","ai-coding","apple","claude-code","codex-skills","cursor-skills","ios","ios-development","liquid-glass","localization","mapkit","networking","storekit","swift","swift-concurrency","swiftdata","swiftui","widgetkit","xcode"],"license":"other","html_url":"https://github.com/dpearson2699/swift-ios-skills","pushed_at":"2026-04-26T21:04:17Z","description":"Agent Skills for iOS 26+, Swift 6.3, SwiftUI, and modern Apple frameworks","skill_md_sha":"c35b68a3f56459078afdfd4c8b58a72adb340b92","skill_md_path":"skills/swiftui-patterns/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/dpearson2699/swift-ios-skills/tree/main/skills/swiftui-patterns"},"layout":"multi","source":"github","category":"swift-ios-skills","frontmatter":{"name":"swiftui-patterns","description":"Builds SwiftUI views with modern MV architecture, state management, and view composition patterns. Covers @Observable ownership rules, @State/@Bindable/@Environment wiring, view decomposition, custom ViewModifiers, environment values, async data loading with .task, iOS 26+ APIs, Writing Tools, and performance guidelines. Use when structuring a SwiftUI app, managing state with @Observable, composing view hierarchies, or applying SwiftUI best practices."},"skills_sh_url":"https://skills.sh/dpearson2699/swift-ios-skills/swiftui-patterns"},"updatedAt":"2026-05-18T18:53:45.950Z"}}