diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 63e2ae4..8738d27 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -11,14 +11,23 @@ { "name": "pdat-swift-practice", "source": "./pdat-swift-practice", - "description": "Practices for SwiftUI, Core Data, XcodeGen and Swift Testing that come from production incidents rather than documentation.", - "version": "0.1.0", + "description": "Curated Apple platform findings with sources, plus practices learned from production incidents.", + "version": "0.2.0", "author": { "name": "PDAT s.r.o." }, "license": "MIT", "category": "development", - "keywords": ["swift", "swiftui", "ios", "macos", "watchos", "core-data", "xcodegen", "swift-testing"] + "keywords": [ + "swift", + "swiftui", + "ios", + "macos", + "watchos", + "core-data", + "xcodegen", + "swift-testing" + ] } ] } diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2680289 --- /dev/null +++ b/LICENSE @@ -0,0 +1,37 @@ +MIT License + +Copyright (c) 2026 PDAT s.r.o. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +--- + +This licence covers what we wrote: the text of the skills, the export +script, and the plugin metadata. + +It does not cover, and this repository does not redistribute: + +- Apple's skills that ship inside Xcode. `bin/export-apple-skills.sh` + exports them from a local installation; they remain Apple's, under + whatever terms accompany the Xcode they came from. Material exported + from a pre-release Xcode is confidential under the Apple Developer + Agreement and must not be published anywhere. +- The articles, talks and documentation cited in `apple-findings`. Those + entries are our own summaries with the source named; the originals + belong to their authors. diff --git a/README.md b/README.md index 951c48a..a1ab7f3 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,14 @@ A Claude Code plugin marketplace for Apple platform work. ## What is here -**`pdat-swift-practice`** — one skill, `xcode-practice`. Practices for +**`pdat-swift-practice`** — two skills. + +`apple-findings` is a curated file of APIs worth adopting that we found +elsewhere: what each one is, what it would replace in our code, whether it +is genuinely a step forward, and where we read about it. Written in our own +words with sources named — see [Sourcing](#sourcing). + +`xcode-practice` is the opposite direction: practices for SwiftUI, Core Data, XcodeGen and Swift Testing that came out of shipping apps: work that must not happen in a view `body`, blobs that must stay off the main thread, derived collections computed once, Keychain access @@ -57,7 +64,25 @@ Push a change, bump `version` in both `.claude-plugin/marketplace.json` and `pdat-swift-practice/.claude-plugin/plugin.json`, then `/plugin marketplace update` locally. Without the version bump, installs will not notice. -## Adding to the skill +## Sourcing + +`apple-findings` collects other people's discoveries, so it follows rules +that keep it honest and lawful: + +- **Write the entry yourself.** An API signature is a fact and facts are + free to restate. The paragraph explaining it is the author's work, and + crediting them does not make copying it acceptable — attribution answers + plagiarism, not copyright. +- **Name the source** — author, title, publication, date — and link it. + Partly courtesy, partly so a stale entry can be checked against the + original later. +- **Say what it replaces in our code.** This is the half no external + article can write, and the reason the file is worth keeping at all. +- **Prefer the vendor's own material where it exists.** When Apple's + shipped skills cover the same API, defer to them and keep only the + "what of ours does this replace" note. + +## Adding to `xcode-practice` The bar is deliberately high: something that actually went wrong, more than once, and whose fix is not obvious from the framework documentation. A rule diff --git a/pdat-swift-practice/.claude-plugin/plugin.json b/pdat-swift-practice/.claude-plugin/plugin.json index 777fabd..bcfd7a6 100644 --- a/pdat-swift-practice/.claude-plugin/plugin.json +++ b/pdat-swift-practice/.claude-plugin/plugin.json @@ -1,14 +1,24 @@ { "name": "pdat-swift-practice", - "version": "0.1.0", - "description": "Practices for SwiftUI, Core Data, XcodeGen and Swift Testing that come from production incidents rather than documentation.", + "version": "0.2.0", + "description": "Curated Apple platform findings with sources, plus practices learned from production incidents.", "author": { "name": "PDAT s.r.o.", "email": "tomas.uvira@pdat.cz" }, "license": "MIT", - "keywords": ["swift", "swiftui", "ios", "macos", "watchos", "core-data", "xcodegen", "swift-testing"], + "keywords": [ + "swift", + "swiftui", + "ios", + "macos", + "watchos", + "core-data", + "xcodegen", + "swift-testing" + ], "skills": [ - "./skills/xcode-practice" + "./skills/xcode-practice", + "./skills/apple-findings" ] } diff --git a/pdat-swift-practice/skills/apple-findings/SKILL.md b/pdat-swift-practice/skills/apple-findings/SKILL.md new file mode 100644 index 0000000..5ae8189 --- /dev/null +++ b/pdat-swift-practice/skills/apple-findings/SKILL.md @@ -0,0 +1,39 @@ +--- +name: apple-findings +description: A curated file of Apple platform APIs worth adopting, each with where we found it and what it would replace in our code — iOS 26 UI affordances (close button role, concentric corners), modern scrolling APIs, and things noted for a later deployment target. Use when reviewing SwiftUI code for outdated patterns, when a hand-built control might have a system equivalent, or when deciding whether a newly published API is worth adopting. +--- + +# Findings + +Things worth adopting that we found elsewhere — release notes, other +people's writing, conference talks — written up in our own words with the +source named, and with the part nobody else can write for us: what it +would replace in our code and whether it is actually a step forward. + +Read `references/` for the individual entries. + +| File | Covers | +|---|---| +| `references/ios26-ui.md` | `Button(role: .close)`, concentric corners | +| `references/scrolling.md` | `ScrollPosition` and friends | +| `references/waiting-on-target.md` | Known, not yet usable at our deployment target | + +## How to add an entry + +**Write it yourself.** Do not paste someone else's prose or their longer +code examples. An API signature is a fact and facts are free; a paragraph +explaining it is their work. Name the source and link it — that is +courtesy and it lets the next person check whether the entry has aged. + +Every entry answers four things: + +1. **What it is** — the API, in one or two sentences. +2. **What it replaces** — the hand-built thing in our code, with the file + if we know it. An entry without this is a bookmark, not a finding. +3. **Whether it is a step forward** — sometimes the honest answer is "no, + ours is fine", and that is worth recording so nobody re-litigates it. +4. **Source** — author, title, link, date read. + +If an entry turns out to be wrong or the API changes, fix it in place and +say so. A stale finding is worse than no finding, because it is quoted +with confidence. diff --git a/pdat-swift-practice/skills/apple-findings/references/ios26-ui.md b/pdat-swift-practice/skills/apple-findings/references/ios26-ui.md new file mode 100644 index 0000000..61de745 --- /dev/null +++ b/pdat-swift-practice/skills/apple-findings/references/ios26-ui.md @@ -0,0 +1,61 @@ +# iOS 26 UI affordances + +## `Button(role: .close)` + +**What it is.** A button role that renders the platform's standard close +control — an `xmark`, positioned and labelled by the system — without +supplying a label or icon. Distinct from `.cancel`, which implies +discarding work; `.close` is for dismissing something informational. + +```swift +.toolbar { + Button(role: .close) { dismiss() } +} +``` + +**What it replaces.** Every hand-assembled close button: an +`Image(systemName: "xmark.circle.fill")` with a hierarchical rendering +mode, a colour chosen to sit on the background, `.buttonStyle(.plain)`, +and a manual `accessibilityLabel("Close")`. We had two of those in one +file alone. + +**Step forward?** Yes, unambiguously. Correct placement per platform, +VoiceOver label and its localisation come from the system, and two +hand-written strings leave the catalogue. Roughly six lines become one. + +**Source.** Natalia Panferova, "Add a Close button to SwiftUI modals on +iOS 26", nilcoalescing.com, 20 Oct 2025. Read 16 Aug 2026. + +## Concentric corners + +**What it is.** Nested rounded rectangles whose inner and outer corner +arcs share a centre, so the gap between them stays even the whole way +round. `ConcentricRectangle` is the shape; `containerShape(.rect(cornerRadius:))` +tells nested views what they are sitting inside so they can derive their +own radius. `isUniform: true` forces all four corners equal, and a +`minimum:` guarantees a floor when the inner shape sits far from the edge. + +```swift +ZStack { + ConcentricRectangle().fill(.thickMaterial) + ConcentricRectangle(corners: .concentric, isUniform: true) + .fill(Color.blue.gradient) +} +.containerShape(.rect(cornerRadius: 24)) +``` + +**What it replaces.** Hand-guessed radii for nested panels. In our card +app: a white "ticket" at radius 16 inside a coloured panel at 24, another +pair at 22 inside 22, tiles at 12 inside 20. Those numbers were picked by +eye at one size and drift apart at any other. + +**Step forward?** Yes, and it is the kind that shows. The Wallet-pass look +we are copying is built on exactly this relationship, and doing it by hand +means re-tuning every time padding changes. Costs nothing at runtime. + +**Caveat.** The container shape must be a rounded-rectangular shape +(`RoundedRectangle`, `Capsule`, `Circle`). + +**Source.** Natalia Panferova, "Corner concentricity in SwiftUI on +iOS 26", nilcoalescing.com, 21 Aug 2025. Read 16 Aug 2026. Apple's own +skills shipped with Xcode 27 do not cover this. diff --git a/pdat-swift-practice/skills/apple-findings/references/scrolling.md b/pdat-swift-practice/skills/apple-findings/references/scrolling.md new file mode 100644 index 0000000..e25df13 --- /dev/null +++ b/pdat-swift-practice/skills/apple-findings/references/scrolling.md @@ -0,0 +1,36 @@ +# Programmatic scrolling + +## `ScrollPosition` instead of `ScrollViewReader` + +**What it is.** A `ScrollPosition` value held in `@State` and attached with +`.scrollPosition($position)` replaces wrapping content in a +`ScrollViewReader` to get at a proxy. Scrolling is then a method call on +the state — `position.scrollTo(id:)` — and, unlike the proxy, the value +can also be read, so the position is available both ways. + +```swift +@State private var position = ScrollPosition() + +ScrollView(.horizontal) { + HStack { ForEach(items) { ItemView(item: $0).id($0.id) } } +} +.scrollPosition($position) +``` + +Related, same family: `.defaultScrollAnchor(.bottom)` for where a view +starts out, and `onScrollGeometryChange(for:of:action:)` (iOS 18) for +following the offset while the user drags. + +**What it replaces.** A `ScrollViewReader` wrapped around content purely +to reach `proxy.scrollTo` from a `.task`. In our card list that is the +filter chip strip, which scrolls the active chip into view. + +**Step forward?** Modest but real. One layer of nesting disappears and the +scroll position stops being write-only. Not urgent — the existing code +works — so worth doing when that view is touched for another reason. + +**Available since.** iOS 17, so nothing blocks it at our deployment +target. `onScrollGeometryChange` needs iOS 18. + +**Source.** Natalia Panferova, "Modern SwiftUI APIs for programmatic +scrolling", nilcoalescing.com, 19 May 2026. Read 16 Aug 2026. diff --git a/pdat-swift-practice/skills/apple-findings/references/waiting-on-target.md b/pdat-swift-practice/skills/apple-findings/references/waiting-on-target.md new file mode 100644 index 0000000..b268706 --- /dev/null +++ b/pdat-swift-practice/skills/apple-findings/references/waiting-on-target.md @@ -0,0 +1,31 @@ +# Known, not yet usable + +Things worth having, blocked on a deployment target. Kept so nobody +rediscovers them from scratch, and so the workarounds they replace can be +deleted the day the target moves. + +## Adaptive toolbars (iOS 27) + +**What it is.** Explicit control over how a toolbar collapses when it runs +out of room: `visibilityPriority` on a `ToolbarItem` (`.low`, `.automatic`, +`.high`, or custom levels declared relative to those), an explicit +`ToolbarOverflowMenu` for what should go into the overflow, and +`ToolbarItem(placement: .topBarPinnedTrailing)` for what must never move. +Related modifiers control minimisation on scroll. + +**What it replaces.** A workaround we already carry: `.secondaryAction` +put our view-options menu into the system's own overflow, where it needed +two taps to open, so it was rebuilt as an ordinary primary-action button +with a hand-placed `ToolbarSpacer`. `visibilityPriority` plus +`ToolbarOverflowMenu` is the supported way to say what we meant. + +**Step forward?** Yes, and it removes a comment explaining a hack. + +**Blocked on.** Deployment target iOS 26. Either raise it or gate with +`#available`, which for a toolbar layout is more trouble than waiting. + +**Source.** Natalia Panferova, "Adaptive SwiftUI toolbars in iOS 27", +nilcoalescing.com, 9 Aug 2026. Read 16 Aug 2026. Apple's +`swiftui-whats-new-27` skill covers the same APIs in more depth — prefer +it once the target moves; this entry exists for the "what of ours does it +replace" note.