Files
claude-plugins/pdat-swift-practice/skills/apple-findings/references/ios26-ui.md
T
TomUviandClaude Opus 5 3ad73df76a Sweep the whole blog, not just the front page
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) <noreply@anthropic.com>
2026-08-18 09:38:55 +02:00

104 lines
4.1 KiB
Markdown

# 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.
## 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.