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>
104 lines
4.1 KiB
Markdown
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.
|