From 3ad73df76a678cbc72c11b942e340608aae89ce9 Mon Sep 17 00:00:00 2001 From: Tomas Uvira Date: Tue, 18 Aug 2026 09:38:55 +0200 Subject: [PATCH] Sweep the whole blog, not just the front page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 153 posts under the SwiftUI tag, plus wwdc25 and wwdc26. Filtering them against what our code actually contains turned up one real defect and a short list worth keeping. The defect is plurals. A checkout screen renders "\(count) coupons", whose Czech translation is the single form "%lld kupónů" — so it says "1 kupónů" and "3 kupónů", wrong for every count below five. The article that surfaced it recommends inflect:, which covers English, Spanish and six others but not Czech, the language that needs three forms rather than two. Plural variations in the String Catalog are the fix for us; the entry says so rather than repeating the article's advice. Two entries are filed against a future rather than now: swipe action buttons draw their titles from iOS 26, which will surprise us the day we add a custom one, and a Form inside a sheet covers the glass behind it, which starts to matter if the payment sheet ever gets a medium detent. The rest is recorded as swept and skipped, with reasons — including that Core Spotlight and the search tab are things we already do correctly, and that three posts are covered better by the skills shipping inside Xcode. A sweep that leaves no trace of what it rejected gets repeated. Co-Authored-By: Claude Opus 5 (1M context) --- .claude-plugin/marketplace.json | 2 +- .../.claude-plugin/plugin.json | 2 +- .../skills/apple-findings/SKILL.md | 2 + .../references/checked-and-skipped.md | 48 +++++++++++++++++ .../apple-findings/references/ios26-ui.md | 42 +++++++++++++++ .../apple-findings/references/localization.md | 52 +++++++++++++++++++ 6 files changed, 146 insertions(+), 2 deletions(-) create mode 100644 pdat-swift-practice/skills/apple-findings/references/checked-and-skipped.md create mode 100644 pdat-swift-practice/skills/apple-findings/references/localization.md diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index d0e8f52..eff0cd2 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -12,7 +12,7 @@ "name": "pdat-swift-practice", "source": "./pdat-swift-practice", "description": "Curated Apple platform findings with sources, plus practices learned from production incidents.", - "version": "0.4.0", + "version": "0.5.0", "author": { "name": "PDAT s.r.o." }, diff --git a/pdat-swift-practice/.claude-plugin/plugin.json b/pdat-swift-practice/.claude-plugin/plugin.json index 5ec5694..d22eb46 100644 --- a/pdat-swift-practice/.claude-plugin/plugin.json +++ b/pdat-swift-practice/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "pdat-swift-practice", - "version": "0.4.0", + "version": "0.5.0", "description": "Curated Apple platform findings with sources, plus practices learned from production incidents.", "author": { "name": "PDAT s.r.o.", diff --git a/pdat-swift-practice/skills/apple-findings/SKILL.md b/pdat-swift-practice/skills/apple-findings/SKILL.md index 5ae8189..a2ceb94 100644 --- a/pdat-swift-practice/skills/apple-findings/SKILL.md +++ b/pdat-swift-practice/skills/apple-findings/SKILL.md @@ -16,7 +16,9 @@ Read `references/` for the individual entries. |---|---| | `references/ios26-ui.md` | `Button(role: .close)`, concentric corners | | `references/scrolling.md` | `ScrollPosition` and friends | +| `references/localization.md` | Plurals, and why `inflect:` does not cover Czech | | `references/waiting-on-target.md` | Known, not yet usable at our deployment target | +| `references/checked-and-skipped.md` | Swept and deliberately not adopted, with reasons | ## How to add an entry diff --git a/pdat-swift-practice/skills/apple-findings/references/checked-and-skipped.md b/pdat-swift-practice/skills/apple-findings/references/checked-and-skipped.md new file mode 100644 index 0000000..03f1ceb --- /dev/null +++ b/pdat-swift-practice/skills/apple-findings/references/checked-and-skipped.md @@ -0,0 +1,48 @@ +# Checked, not adopted + +The sweep through nilcoalescing.com covered 153 posts tagged SwiftUI plus +the wwdc25 and wwdc26 tags. Most of it does not apply to us. This file +records the ones worth explaining, so the next sweep does not re-open +them. + +## Already doing it + +- **Core Spotlight indexing** — we index cards and handle + `onContinueUserActivity(CSSearchableItemActionType)` already. +- **The iOS 26 search tab** — we use `Tab(role: .search)` with + `searchable()`, which is what the article recommends. Its other + suggestions (`searchToolbarBehavior()`, `DefaultToolbarItem(kind: .search)`) + are for apps that keep search in a toolbar rather than a tab. + +Re-reading these was not wasted: confirming that a decision still matches +current guidance is worth something, and takes a minute. + +## Apple's own skills cover it better + +Three posts overlap directly with the skills that ship inside Xcode, which +go deeper and are maintained with the SDK. Prefer those. + +| Post | Covered by | +|---|---| +| Equatable properties in @Observable classes | `swiftui-specialist/references/dataflow.md` | +| The hidden cost of unstable environment defaults | `swiftui-specialist/references/environment.md` | +| Custom bindings: closures vs subscripts | `swiftui-specialist/references/dataflow.md` | + +The binding one is a good example of why the Apple skills earn their +place: it describes the exact anti-pattern we removed from four sheets — +`Binding(get:set:)` built inside `body` — and we found it through the +skill during review, before the article ever came up. + +## Not our shape + +Blend modes, mesh gradients, `TimelineView`, macOS window and menu-bar +work, `Canvas` frame reprojection, MapKit polyline gradients, UIKit +interop. Good writing, nothing to act on here. + +## Filed elsewhere + +- Dynamic Type truncation — `fixedSize(horizontal: false, vertical: true)` + is the fix when text in a scrollable container gets cut off at + accessibility sizes. Our card tiles deliberately use + `minimumScaleFactor` instead, which is right for a fixed-height tile. + Worth remembering for descriptive text, not for the tiles. diff --git a/pdat-swift-practice/skills/apple-findings/references/ios26-ui.md b/pdat-swift-practice/skills/apple-findings/references/ios26-ui.md index 61de745..2e1cb66 100644 --- a/pdat-swift-practice/skills/apple-findings/references/ios26-ui.md +++ b/pdat-swift-practice/skills/apple-findings/references/ios26-ui.md @@ -59,3 +59,45 @@ means re-tuning every time padding changes. Costs nothing at runtime. **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. + +## Swipe action buttons now show their title + +**What it is.** Before iOS 26, a `Button("Delete", systemImage: "trash")` +in a swipe action rendered as the symbol alone. From iOS 26 it draws the +title too, stacked or side by side depending on row height. To get the +old look back, say so explicitly: + +```swift +Button("Delete", systemImage: "trash", role: .destructive) { … } + .labelStyle(.iconOnly) +``` + +**What it replaces.** Nothing of ours yet — our rows use `.onDelete`, so +the system supplies the button and this does not apply. Recorded because +the moment we add a custom swipe action, the rows will look wider than +intended and the cause will not be obvious. + +**Source.** Natalia Panferova, "Show icons only in SwiftUI swipe actions +on iOS 26", nilcoalescing.com, 15 Oct 2025. Read 18 Aug 2026. + +## A Form inside a sheet hides the glass behind it + +**What it is.** `Form` and `List` paint their own opaque background, which +covers the Liquid Glass surface of the sheet they sit in. Clearing it +takes two modifiers, because a pushed navigation destination adds a second +opaque layer of its own: + +```swift +.scrollContentBackground(.hidden) +.containerBackground(.clear, for: .navigation) +``` + +**What it replaces.** Nothing yet. Our sheets that pair `NavigationStack` +with `Form` — the payment code sheet, the card editor — are presented at +full height, where the opaque background is what you want anyway. It +starts to matter the day one of them gets a medium detent, which is +plausible for the payment sheet: the amount is three fields, and a +half-height sheet would keep the card visible behind it. + +**Source.** Natalia Panferova, "SwiftUI Liquid Glass sheets with +NavigationStack and Form", nilcoalescing.com, 9 Sep 2025. Read 18 Aug 2026. diff --git a/pdat-swift-practice/skills/apple-findings/references/localization.md b/pdat-swift-practice/skills/apple-findings/references/localization.md new file mode 100644 index 0000000..5317038 --- /dev/null +++ b/pdat-swift-practice/skills/apple-findings/references/localization.md @@ -0,0 +1,52 @@ +# Localization + +## Plurals: inflection is not enough for Czech + +**What it is.** SwiftUI parses an inflection attribute inside a `Text` +literal and applies Foundation's grammar agreement, so the noun follows +the number without any manual branching: + +```swift +Text("You read ^[\(bookCount) book](inflect: true) this year!") +``` + +**Language support is the catch.** It shipped for English and Spanish in +iOS 15, and iOS 18 added German, French, Italian, Portuguese, Hindi and +Korean. **Czech is not on that list**, and Czech is exactly the language +that needs it: 1 kupón, 2–4 kupóny, 5+ kupónů — three forms where English +has two. + +**What it found in our code.** A real bug, not a nicety: + +```swift +Text("\(activeCoupons.count) coupons") // CardDetailView, checkout screen +``` + +The Czech translation in the catalogue is a single string, `%lld kupónů`, +so the app renders "1 kupónů" and "3 kupónů" — wrong for every count +except five and up. It sits on the checkout screen, which is the screen +people actually look at in a shop. + +**The fix for us** is plural variations in the String Catalog rather than +`inflect:`. Xcode's "Vary by Plural" writes a `variations.plural` block +with `one` / `few` / `many` / `other`, and Czech uses all of them: + +| category | counts | Czech | +|---|---|---| +| one | 1 | 1 kupón | +| few | 2–4 | 3 kupóny | +| many | fractions | 1,5 kupónu | +| other | 0, 5+ | 5 kupónů | + +Worth auditing every interpolated count in the UI at the same time; a +count wrapped in parentheses — "Send unsent receipts (3)" — dodges the +problem entirely and is a reasonable fallback where a plural form would +be awkward. + +**Step forward?** Yes, and overdue. This is the kind of defect that never +shows up in a test and that a Czech speaker notices immediately. + +**Source.** Natalia Panferova, "Handle plurals in SwiftUI Text views with +inflection", nilcoalescing.com, 13 Jan 2025. Read 18 Aug 2026. The article +is about `inflect:`; the Czech conclusion is ours, from checking which +languages that API actually covers.