Communitygithub.com

swiftui-conzentrische-ecken

Konzentrische Ecken in SwiftUI (iOS 26+): ConcentricRectangle, containerShape, Edge.Corner.Style.concentric(minimum:) und eine wiederverwendbare Ecken-Skala, sodass geschachtelte Formen – ein Foto in einer Karte, eine schwebende Karte auf einer Karte, ein Thumbnail in dieser Karte, ein Hero-Bild nahe dem Bildschirmrand – ihre Eckenmittelpunkte teilen, anstatt gequetscht oder ausgefranst auszusehen.

Was ist swiftui-conzentrische-ecken?

Diese SwiftUI-Fähigkeit (iOS 26+ / macOS 26+) löst das häufige Designproblem von geschachtelten abgerundeten Formen, deren Ecken falsch aussehen: entweder aufgeweitet (innen runder als außen) oder eingeklemmt (innen deutlich eckiger). Sie führt das Konzept der konzentrischen Ecken ein, bei dem der Innenradius der Differenz zwischen Außenradius und Padding entspricht. Der Skill bietet die neuen APIs `ConcentricRectangle`, `containerShape` und `Edge.Corner.Style.concentric(minimum:)`, die Apple in der WWDC25-Session „Get to know the new design system“ und im iOS 26 SDK vorgestellt hat. Zusätzlich enthält er eine wiederverwendbare Ecken-Skala (z. B. `Corners.inner`, `Corners.map`, `Corners.container(padding:)`), mit der Entwickler konsistente Radius-Token für Designsysteme ableiten können. Der Skill unterstützt alle wichtigen SwiftUI-Plattformen (iOS 26+, macOS 26+) und funktioniert über eine einfache Checkliste und Verifikationshinweise, die sicherstellen, dass Container ihre Form deklarieren, geschachtelte Inhalte `ConcentricRectangle` verwenden und Fallbacks für iOS 18 bereitstehen.

Funktioniert mit~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/gtokman/skills/tree/HEAD/swiftui-concentric-corners

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Dokumentation

Konzentrische Ecken

Geschachtelte abgerundete Formen sehen richtig aus, wenn sie ihre Eckenmittelpunkte teilen: Der Innenradius ist der Außenradius minus dem Abstand zwischen ihnen. Wird dies falsch gemacht, sehen die Ecken aufgeweitet (innen runder als außen) oder eingeklemmt (innen deutlich eckiger) aus. Die drei Formtypen von Apple:

  • Fest – ein konstanter Radius (RoundedRectangle(cornerRadius:)).
  • Kapsel – der Radius ist die Hälfte der Höhe; von Natur aus konzentrisch (Buttons, Badges, Pills).
  • Konzentrisch – Radius = Elternradius − Padding, vom System berechnet.

Die API (iOS 26, macOS 26)

// Der Container deklariert seine Form. Muss ein RoundedRectangularShape sein:
// RoundedRectangle, UnevenRoundedRectangle, Rectangle, Capsule, Circle.
card.containerShape(RoundedRectangle(cornerRadius: 28, style: .continuous))

// Der Inhalt darin ist konzentrisch dazu, nie unter dem Minimum (sein Radius, wenn
// er eigenständig oder weit von einer Containerecke entfernt ist).
ConcentricRectangle(corners: .concentric(minimum: .fixed(16)), isUniform: false)
  • .concentric(minimum:) ist eine Untergrenze, kein Ziel: Radius = max(Container − Einzug, Minimum). Wenn der Container zu eng für das Padding des Inhalts ist, erhöhe den Radius des Containers – senke nicht das Minimum.
  • isUniform: true gibt jeder Ecke denselben Radius (eine Karte, die nahe einer Kante einer Karte schwebt); false macht nur die Ecken konzentrisch, die nahe den Containerecken liegen (Apples Sheets).
  • Ohne containerShape werden konzentrische Formen gegen die Fenster-/Displayecken aufgelöst – so kann der obere Inhalt einer Vollbildansicht der Geräteabrundung folgen.
  • ConcentricRectangle ist kein RoundedRectangularShape: Es kann kein Container sein. Ein Container, der selbst geschachtelt ist (eine Karte auf einer Karte), deklariert seinen resultierenden Radius als RoundedRectangle (Kartenradius − Einzug) für seine eigenen Kinder.
  • matchedTransitionSource { $0.clipShape(…) } akzeptiert nur RoundedRectangle – gib ihm den üblichen Radius des Inhalts.
  • Vor iOS 26: Fallback auf RoundedRectangle mit dem Mindestradius. containerShape auf einem RoundedRectangle kompiliert trotzdem (die ältere InsettableShape-Überladung).

Eine wiederverwendbare Ecken-Skala

Leite Container aus Inhalten ab, nicht umgekehrt: Wähle einen inneren Radius für Fotos und kleine Karten, und mache jeden Container zu inner + padding. Dann kommt alles, was ein Padding entfernt ist, exakt bei inner an.

enum Corners {
    static let inner: CGFloat = 16          // Fotos, kleine Karten
    static let map: CGFloat = 28            // Karten und große Medien
    static let mapInset: CGFloat = 12       // Karten auf einer Karte → 16

    static func container(padding: CGFloat) -> CGFloat { inner + padding }   // 12 pt → 28

    static func rect(_ radius: CGFloat) -> RoundedRectangle {
        RoundedRectangle(cornerRadius: radius, style: .continuous)
    }

    /// Inhalt, der in einem Container geschachtelt ist: konzentrisch, nie unter `minimum`.
    static func nested(minimum: CGFloat, uniform: Bool = false) -> AnyShape {
        if #available(iOS 26, *) {
            AnyShape(ConcentricRectangle(corners: .concentric(minimum: .fixed(minimum)), isUniform: uniform))
        } else {
            AnyShape(rect(minimum))
        }
    }
}

Verwendung:

// Eine Karte: Radius aus ihrem Padding, als Container deklariert.
let shape = Corners.rect(max(requested, Corners.container(padding: padding)))
VStack { photo }.padding(padding)
    .background(shape.fill(.surface))
    .containerShape(shape)

// Ein Foto überall: konzentrisch mit dem, was es hält, eigenständig 16.
let photo = Corners.nested(minimum: Corners.inner)
photo.fill(.placeholder).overlay { image }.clipShape(photo)

// Eine Karte mit einer schwebenden Glaskarte, die ein Thumbnail enthält.
map.clipShape(Corners.rect(Corners.map)).containerShape(Corners.rect(Corners.map))
    .overlay(alignment: .bottom) {
        HStack { Corners.nested(minimum: 10).fill(…).frame(width: 64, height: 64) … }
            .padding(12)
            .glassEffect(.regular, in: Corners.nested(minimum: Corners.map - Corners.mapInset, uniform: true))
            .containerShape(Corners.rect(Corners.map - Corners.mapInset))
            .padding(Corners.mapInset)
    }

Checkliste

  • Jeder Container, der abgerundeten Inhalt enthält, deklariert .containerShape(_:).
  • Geschachtelter Inhalt verwendet ConcentricRectangle (oder eine Hilfsfunktion) mit einem sinnvollen minimum.
  • Containerradius ≥ Inhaltsradius + Padding; niemals ein größerer Radius innerhalb eines kleineren.
  • Clip, Hintergrund, Glas und Hit-Testing (contentShape) verwenden dieselbe Form.
  • Kapseln für Buttons, Badges und Pills; zusätzlicher Rand vom Bildschirmrand auf Telefonen.
  • Fallback für iOS 18 vorhanden.

Überprüfung

Rendere offscreen auf macOS 26+ (ImageRenderer, #available(iOS 26, macOS 26, *) in einer Kopie der Hilfsfunktion) mit kontrastreichen Füllungen: ein Container, Inhalt ein Padding entfernt, derselbe Inhalt eigenständig und eine schwebende Karte in einem größeren Container. Die Ecken sollten sichtbar die Mittelpunkte teilen; die eigenständige zeigt das Minimum.

Individual skills in this repo

This repo contains 1 individual skill — each has its own dedicated page.

Verwandte Skills