Merge learnings from Apple's Xcode 27 swiftui-specialist skill - #70
Conversation
…alist skill Fold the strongest guidance from Apple's bundled swiftui-specialist skill into the SwiftUI Expert Skill, deduplicating overlaps and fixing stale advice. Fixes / corrections: - list-patterns: `.enumerated()` no longer needs Array() on Swift 6.1+, and the id must be the element's identity, not `\.offset` (same trap as `.indices`) - list-patterns: make the unified-row example unary (wrap branching in a container) so List can template row ids - view-structure: soften the property-ordering mandate to an optional readability suggestion (per AGENTS.md, no formatting rules) Sharpened: - view-structure: invalidation-boundary framing for extracting subviews, plus the multi-section detail-view anti-pattern and a "don't auto-refactor .if" review note - performance-patterns: full rapidly-updating-environment pattern (coarsened @observable thresholds, per-item models, scrollTransition/visualEffect) - animation-advanced: when to implement animatableData manually, AnimatableValues (iOS 26+) vs AnimatablePair Added: - state-management: make @observable property types Equatable (setter short-circuit), per-property observation granularity traps + fixes, KeyPath bindings over closure bindings, no closures in custom environment keys, stable @entry defaults, removing unused @Environment reads - view-structure: keep view init cheap; single-child Group anti-pattern - list-patterns: unary vs multi rows, -LogForEachSlowPath, stable/cheap ids - new references/localization.md and references/soft-deprecation.md - SKILL.md Topic Router, References list, Correctness Checklist, and trigger description updated for the new coverage
There was a problem hiding this comment.
Pull request overview
Integrates and de-duplicates guidance from Apple’s Xcode 27 swiftui-specialist skill into this repo’s swiftui-expert-skill, expanding coverage around invalidation/observation, list identity, and localization while adding new reference docs and cross-links.
Changes:
- Expanded the skill router/checklists and refreshed guidance across view structure, state management, lists, performance, and animations.
- Added new reference docs for localization and soft-deprecation behavior, plus cross-references from existing docs.
- Updated repository documentation to reflect the expanded reference set.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| swiftui-expert-skill/SKILL.md | Updates trigger description, topic router, and correctness checklist to include new/expanded areas (localization, soft-deprecation, list identity rules). |
| swiftui-expert-skill/references/view-structure.md | Adds invalidation-boundary framing, review guidance around .if modifiers, and new sections on cheap init and single-child Group. |
| swiftui-expert-skill/references/text-patterns.md | Adds a cross-link to the new localization reference to keep scope clear. |
| swiftui-expert-skill/references/state-management.md | Adds new sections on @Observable granularity and environment key patterns (@Entry, closures, defaults), plus updated key principles. |
| swiftui-expert-skill/references/soft-deprecation.md | New reference defining “soft-deprecation” behavior and scoping rules. |
| swiftui-expert-skill/references/performance-patterns.md | Clarifies guidance around environment invalidation costs and introduces coarsening patterns for high-frequency values. |
| swiftui-expert-skill/references/localization.md | New reference covering String Catalogs, bundles (packages/frameworks), formatting, RTL layout, and translator comments. |
| swiftui-expert-skill/references/list-patterns.md | Updates list/ForEach identity guidance (unary rows, enumerated identity) and strengthens the checklist. |
| swiftui-expert-skill/references/latest-apis.md | Adds a behavioral cross-reference to soft-deprecation guidance and the maintenance skill. |
| swiftui-expert-skill/references/animation-advanced.md | Adds guidance for when to implement animatableData manually and references AnimatableValues vs AnimatablePair. |
| README.md | Updates the reference structure listing to include newly added reference docs. |
| Reach for an explicit `animatableData` (instead of the macro) when the interpolated value needs custom logic that doesn't map 1:1 to a stored property — normalization, clamping, or driving a derived value. For a deployment target of iOS 26+, use `AnimatableValues`; for earlier targets, use `AnimatablePair`. | ||
|
|
||
| ```swift | ||
| // iOS 26+: keep phase in 0..<2π and clamp amplitude during interpolation | ||
| struct WaveShape: Shape { | ||
| var amplitude: CGFloat | ||
| var phase: CGFloat | ||
| var maxAmplitude: CGFloat | ||
|
|
||
| var animatableData: AnimatableValues<CGFloat, CGFloat> { | ||
| get { AnimatableValues(amplitude, phase) } | ||
| set { | ||
| amplitude = min(max(newValue.value.0, 0), maxAmplitude) | ||
| phase = newValue.value.1.truncatingRemainder(dividingBy: 2 * .pi) | ||
| } | ||
| } |
| ``` | ||
|
|
||
| **No `Array(...)` wrapper is needed on Swift 6.1+.** As of Swift 6.1, the sequence returned by `.enumerated()` conditionally conforms to `RandomAccessCollection` when the base collection does, so `ForEach` accepts it directly. On earlier toolchains, wrap it in `Array(...)`. Favor the direct form in new code — it avoids an eager copy on every body evaluation. | ||
|
|
| - [ ] No inline filtering in ForEach (prefilter and cache instead) | ||
| - [ ] No `AnyView` in list rows | ||
| - [ ] Don't convert enumerated sequences to arrays | ||
| - [ ] `.enumerated()` uses the element's id (not `\.offset`); no `Array(...)` wrapper needed on Swift 6.1+ | ||
| - [ ] Use `.refreshable` for pull-to-refresh |
| 7. With `@Observable`, nested objects work fine; with `ObservableObject`, pass nested objects directly to child views | ||
| 8. **Always add `@ObservationIgnored` to property wrappers** (e.g., `@AppStorage`, `@SceneStorage`, `@Query`) inside `@Observable` classes — they conflict with the macro's property transformation | ||
| 9. **Prefer `Equatable` types for frequently-written `@Observable` properties** so the generated setter skips redundant invalidations | ||
| 10. **Never store closures in custom environment keys; keep `@Entry` defaults stable** (no `Model()`/`Date()` expressions) | ||
| 11. **Prefer KeyPath/subscript bindings over closure bindings** |
| The `@Observable` macro generates a setter that **skips invalidation when the new value equals the current one** — but only when it can compare them, which means only when the property's type is `Equatable`. Without that conformance, every assignment notifies observing views, even when the value is identical. This is an easy win for properties written frequently with the same value (polling, streaming updates, timers). | ||
|
|
||
| ```swift | ||
| // AVOID: not Equatable — every assignment invalidates, even no-op writes | ||
| enum DeliveryStatus { case placed, preparing, shipped, delivered } | ||
|
|
||
| // PREFER: Equatable lets the generated setter short-circuit redundant writes | ||
| enum DeliveryStatus: Equatable { case placed, preparing, shipped, delivered } | ||
| ``` | ||
|
|
||
| This applies to collection properties too: an `Array`/`Set`/`Dictionary` is only `Equatable` when its element type is, so a non-`Equatable` element defeats the short-circuit for the whole collection. (The check is emitted into the generated setter as user code, so it applies on every OS that supports `@Observable` when built with current Xcode.) | ||
|
|
||
| This is distinct from `Equatable` *views* (see `references/performance-patterns.md`): that conformance lets SwiftUI skip a view's body; this one lets the model skip notifying observers in the first place. |
|
Thanks @copilot — I went through all five comments. Each one challenges guidance that was ported verbatim from Apple's Xcode 27 1. 2 & 3. 4 & 5. Source of truth for all four areas is Apple's bundled |
Summary
Folds the strongest guidance from Apple's bundled Xcode 27
swiftui-specialistskill into ourswiftui-expert-skill, aiming for the best of both: our breadth (layout, navigation, charts, macOS, previews, Instruments traces) plus Apple's depth on the invalidation model. Overlaps were de-duplicated with cross-links, and a few stale/conflicting points were corrected.Fixes / corrections
list-patterns:.enumerated()no longer needs anArray(...)wrapper on Swift 6.1+, and the id must be the element's identity (\.element.id), not\.offset— the old advice was the same position-as-id trap as.indices.list-patterns: the "unified row" example is now unary (branching wrapped in a container) soListcan template row ids.view-structure: softened the property-ordering mandate to an optional readability suggestion, per our ownAGENTS.md(no formatting/linting rules).Sharpened
view-structure: invalidation-boundary framing for extracting subviews; multi-section detail-view anti-pattern; "don't auto-refactor an existing.ifduring unrelated work" review note.performance-patterns: full rapidly-updating-environment pattern (coarsened@Observablethresholds, per-item models,scrollTransition/visualEffect).animation-advanced: when to implementanimatableDatamanually;AnimatableValues(iOS 26+) vsAnimatablePair.Added
state-management: make@Observableproperty typesEquatable(setter short-circuit), per-property observation granularity traps + fixes, KeyPath bindings over closure bindings, no closures in custom environment keys, stable@Entrydefaults, removing unused@Environmentreads.view-structure: keep viewinitcheap; single-childGroupanti-pattern.list-patterns: unary vs multi rows,-LogForEachSlowPath, stable/cheap/outliving ids.references/localization.mdandreferences/soft-deprecation.md.SKILL.md: Topic Router rows, References list, Correctness Checklist, and trigger description updated for the new coverage.Notes / follow-ups
references/latest-apis.mdwas not hand-edited; refreshing it against the iOS 27 SDK should be run through the existing.agents/skills/update-swiftui-apismaintenance skill (Sosumi-driven) as a separate change. A behavioral cross-reference to the newsoft-deprecation.mdwas added.AGENTS.md(SwiftUI-only, factual not architectural, optimizations framed as suggestions).Test plan
git difffor tone/accuracy and confirm no contradictions with existing sectionsreferences/*.mdlinks inSKILL.mdresolve (they do)state-management.mdandview-structure.mdSKILL.mddescription