Hinter der Insel läuft jetzt das Wasser durch

Der schwarze Streifen über der Navigation war kein gezeichneter Balken,
sondern fehlender Inhalt: der gesamte Baum saß innerhalb des
Sicherheitsrands. Deshalb erreichte kein noch so gut gesetzter Rand
weiter unten die Fläche darüber — Inhalt kam dort nie hin.

Jetzt reißt der HStack den Rand oben auf und gibt den Platz kontrolliert
zurück: Seitenleiste und Insel bekommen ihren Abstand zur Statusleiste
explizit, der Inhalt bekommt ihn als Sicherheitsrand. Der Aufmacher
nimmt ihn sich wieder — um genau diese Strecke höher und um genau diese
Strecke größer, sodass unten alles bleibt, wo es war. Dazu ein knapper
Verlauf oben, damit Uhrzeit und Batterie auch über einem hellen Bild
lesbar bleiben.

Und die Leiste oben fährt beim Scrollen weg. Nur sie: der
Sicherheitsrand darunter bleibt stehen, sonst ruckte die Seite bei jedem
Ein- und Ausblenden unter dem Finger weg.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-09-13 22:52:07 +02:00
co-authored by Claude Opus 5
parent 9b3c6e2030
commit 81e22c4f97
13 changed files with 211 additions and 8 deletions
+26 -2
View File
@@ -64,6 +64,23 @@ struct HeroCarousel: View {
@Environment(\.flixyAccent) private var accent
@Environment(FlixySession.self) private var session
@Environment(\.horizontalSizeClass) private var sizeClass
@Environment(TopChrome.self) private var chrome
/// Wie weit der Aufmacher nach oben unter die Insel greift.
///
/// Der Inhalt beginnt unter der Insel — das ist für Reihen und Listen
/// richtig, für den Aufmacher aber nicht: er ist das Bild, auf dem die
/// Insel schweben soll, und ein Bild, das an ihrer Unterkante anfängt,
/// lässt darüber einen schwarzen Streifen stehen, den nie etwas füllt.
///
/// Also nimmt er sich den Platz zurück: um genau diese Strecke höher und
/// um genau diese Strecke größer. Unten ändert sich dadurch nichts, die
/// Reihen bleiben, wo sie waren.
///
/// Auf dem iPhone null — dort schwebt nichts über dem Inhalt.
private var overlap: CGFloat {
sizeClass == .compact ? 0 : chrome.inset
}
private var currentIndex: Int {
items.firstIndex { $0.id == shownID } ?? 0
@@ -95,7 +112,7 @@ struct HeroCarousel: View {
//
// Auf einem schmalen Gerät nicht: dort ist der Aufmacher hochkant
// und rechnet sich seine Höhe aus der Breite aus.
.modifier(HeroHeight(fraction: sizeClass == .compact ? nil : 0.82))
.modifier(HeroHeight(fraction: sizeClass == .compact ? nil : 0.82, extra: overlap))
// **Kein** Seitenverhältnis hier: der Aufmacher bringt sein
// eigenes mit, und eines darüber legte ihn auf die Breite des
// Inhaltsbereichs fest. Weil er nach links unter die Seitenleiste
@@ -136,6 +153,10 @@ struct HeroCarousel: View {
if items.count > 1 { indicator }
}
// Zieht den ganzen Aufmacher unter die Insel. Zusammen mit der
// zusätzlichen Höhe oben hebt sich das unten auf: er beginnt am
// Bildschirmrand und endet, wo er vorher endete.
.padding(.top, -overlap)
.task(id: shownID) { await advance() }
.onChange(of: items.map(\.id), initial: true) { _, ids in
// Beim ersten Zeichnen und nach jedem Aktualisieren: steht die
@@ -217,10 +238,13 @@ struct HeroCarousel: View {
/// Drehen neu geladen statt mitzuwandern.
private struct HeroHeight: ViewModifier {
let fraction: CGFloat?
/// Was über den Anteil hinaus dazukommt: der Platz unter der Insel, den
/// der Aufmacher mitfüllt, weil er nach oben unter sie greift.
let extra: CGFloat
func body(content: Content) -> some View {
if let fraction {
content.containerRelativeFrame(.vertical) { height, _ in height * fraction }
content.containerRelativeFrame(.vertical) { height, _ in height * fraction + extra }
} else {
content
}
+1
View File
@@ -73,6 +73,7 @@ struct HomeView: View {
.padding(.bottom, FlixySpacing.section)
}
.scrollIndicators(.never)
.flixyRevealsTopChrome()
}
/// Bringt die Reihen vom Server in die Form, die eingestellt ist.