Collect findings from other people's writing, with sources
The skill so far only held what we broke ourselves. Most of what is worth knowing about a new SDK is found rather than suffered — release notes, other people's articles — and that deserves a place too, on terms that stay fair to whoever wrote it. So: entries in our own words, source named and linked, and the one thing no external article can supply — what it would replace in our code and whether it is genuinely a step forward. Attribution answers plagiarism, not copyright, so nothing is pasted; an API signature is a fact, the paragraph explaining it is not ours to take. First four entries come from Natalia Panferova's writing at nilcoalescing .com: the close button role and concentric corners, both of which replace things we hand-built; ScrollPosition, which replaces one wrapper view; and the iOS 27 toolbar APIs, which we cannot use at our deployment target but which would delete a workaround the day we can. A LICENSE was overdue and now says plainly what is ours, what is Apple's and stays out of the repository, and what belongs to the authors we cite. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
905ad2ce3e
commit
f84852d49c
8 files changed
+257
-9
No files matched your search
@@ -11,14 +11,23 @@
|
|||||||
{
|
{
|
||||||
"name": "pdat-swift-practice",
|
"name": "pdat-swift-practice",
|
||||||
"source": "./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.",
|
"description": "Curated Apple platform findings with sources, plus practices learned from production incidents.",
|
||||||
"version": "0.1.0",
|
"version": "0.2.0",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "PDAT s.r.o."
|
"name": "PDAT s.r.o."
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"category": "development",
|
"category": "development",
|
||||||
"keywords": ["swift", "swiftui", "ios", "macos", "watchos", "core-data", "xcodegen", "swift-testing"]
|
"keywords": [
|
||||||
|
"swift",
|
||||||
|
"swiftui",
|
||||||
|
"ios",
|
||||||
|
"macos",
|
||||||
|
"watchos",
|
||||||
|
"core-data",
|
||||||
|
"xcodegen",
|
||||||
|
"swift-testing"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -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.
|
||||||
@@ -4,7 +4,14 @@ A Claude Code plugin marketplace for Apple platform work.
|
|||||||
|
|
||||||
## What is here
|
## 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
|
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
|
apps: work that must not happen in a view `body`, blobs that must stay off
|
||||||
the main thread, derived collections computed once, Keychain access
|
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
|
`pdat-swift-practice/.claude-plugin/plugin.json`, then `/plugin marketplace
|
||||||
update` locally. Without the version bump, installs will not notice.
|
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
|
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
|
once, and whose fix is not obvious from the framework documentation. A rule
|
||||||
|
|||||||
@@ -1,14 +1,24 @@
|
|||||||
{
|
{
|
||||||
"name": "pdat-swift-practice",
|
"name": "pdat-swift-practice",
|
||||||
"version": "0.1.0",
|
"version": "0.2.0",
|
||||||
"description": "Practices for SwiftUI, Core Data, XcodeGen and Swift Testing that come from production incidents rather than documentation.",
|
"description": "Curated Apple platform findings with sources, plus practices learned from production incidents.",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "PDAT s.r.o.",
|
"name": "PDAT s.r.o.",
|
||||||
"email": "tomas.uvira@pdat.cz"
|
"email": "tomas.uvira@pdat.cz"
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"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": [
|
||||||
"./skills/xcode-practice"
|
"./skills/xcode-practice",
|
||||||
|
"./skills/apple-findings"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
Reference in new issue
Block a user