Der Schriftzug gehörte zum vorigen Film — ein Zustand ohne Adresse

„Werner Beinhart" über dem Bild von John Wick. Von mir eingebaut, im
Commit, der die Überblendung umgestellt hat.

**Der Grund ist der Umbau selbst.** Vorher bekam der Aufmacher je Titel
eine neue Identität; sein Zustand begann damit jedes Mal von vorn. Seit
er bleibt und nur seine Angaben wechseln, überlebt auch der geladene
Schriftzug den Wechsel — und `availableLogo` gab ihn heraus, ohne zu
prüfen, ob er überhaupt zum aktuellen Titel gehört:

    if let logo { return logo }        // ← zu irgendeinem Film

Jetzt trägt der Zustand die Adresse mit, zu der er gehört, und wird nur
herausgegeben, wenn sie zur aktuellen passt. Passt sie nicht, gilt der
Zwischenspeicher — und wenn auch der nichts hat, der gesetzte Titel. Nie
der falsche Schriftzug.

**Dieselbe Falle steckte in `FlixyArtwork`**, nur unauffälliger: auch
dort hielt `loaded` ein Bild ohne Angabe, wozu es gehört, und `load()`
brach deswegen früh ab („ist ja schon eins da"). Solange jede Kachel ihre
eigene Identität hat, fällt das nicht auf. Der Aufmacher hat seit dem
Umbau keine mehr.

Die Regel dahinter gilt allgemein: **ein Zustand, der etwas
zwischenspeichert, muss sagen, wozu es gehört** — sonst gilt er für
alles.

Die Attrappe liefert jetzt Schriftzüge (`Logo`-Bildmarke). Ohne sie fällt
der Aufmacher auf den gesetzten Titel zurück, und dieser ganze Weg wäre
im Simulator gar nicht zu sehen — genau deshalb ist der Fehler erst beim
Nutzer aufgefallen.

iOS und tvOS bauen, 476 Tests grün.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-10-01 12:29:26 +02:00
co-authored by Claude Opus 5
parent 37bb153dd8
commit 2ab05ee8f6
3 changed files with 30 additions and 7 deletions
@@ -68,7 +68,14 @@ public struct FlixyArtwork: View {
self.contentMode = contentMode
}
@State private var loaded: PlatformImage?
/// Das geladene Bild — **mit den Adressen, zu denen es gehört**.
///
/// **Ohne sie wird es falsch,** sobald eine Ansicht ihre Identität über
/// einen Inhaltswechsel hinweg behält: der Zustand überlebt den Wechsel,
/// und das alte Bild bliebe stehen, bis das neue geladen ist. Solange
/// jede Kachel ihre eigene Identität hatte, fiel das nicht auf — der
/// Aufmacher hat seit dem Umbau der Überblendung keine mehr.
@State private var loaded: (urls: [URL], image: PlatformImage)?
@State private var hasFailed = false
public var body: some View {
@@ -96,7 +103,7 @@ public struct FlixyArtwork: View {
/// ersten Bild und blitzt nicht kurz grau auf. Über ein `await` ginge das
/// nicht — das kostet immer einen Durchlauf.
private var available: PlatformImage? {
if let loaded { return loaded }
if let loaded, loaded.urls == urls { return loaded.image }
// Die erste, die schon im Speicher liegt — in der Reihenfolge der
// Vorlieben, nicht in der des Zufalls.
for url in urls {
@@ -147,7 +154,7 @@ public struct FlixyArtwork: View {
let bild = await ArtworkLoader.shared.image(for: url)
guard !Task.isCancelled else { return }
if let bild {
withAnimation(.easeOut(duration: 0.2)) { loaded = bild }
withAnimation(.easeOut(duration: 0.2)) { loaded = (urls, bild) }
return
}
}
@@ -53,7 +53,16 @@ public struct HeroBanner<Actions: View>: View {
private let fillsAvailableSpace: Bool
private let actions: Actions
@State private var logo: PlatformImage?
/// Der geladene Schriftzug — **mit der Adresse, zu der er gehört**.
///
/// **Ohne die Adresse wird er falsch.** Seit der Aufmacher seine
/// Identität über den Titelwechsel hinweg behält, überlebt auch dieser
/// Zustand ihn: der Schriftzug des vorigen Films stand dann über dem Bild
/// des neuen, bis der neue geladen war. „Werner Beinhart" über John Wick.
///
/// Ein Zustand, der eine andere Angabe zwischenspeichert, muss sagen,
/// wozu er gehört — sonst gilt er für alles.
@State private var loadedLogo: (url: URL, image: PlatformImage)?
public init(
title: String,
@@ -143,8 +152,8 @@ public struct HeroBanner<Actions: View>: View {
/// Bild des Übergangs da. Nur wenn nicht, wird nachgeladen — und dann
/// steht bis dahin der gesetzte Titel.
private var availableLogo: PlatformImage? {
if let logo { return logo }
guard let logoURL else { return nil }
if let loadedLogo, loadedLogo.url == logoURL { return loadedLogo.image }
return ArtworkLoader.cachedImage(for: logoURL)
}
@@ -224,7 +233,9 @@ public struct HeroBanner<Actions: View>: View {
}
.task(id: logoURL) {
guard let logoURL, ArtworkLoader.cachedImage(for: logoURL) == nil else { return }
logo = await ArtworkLoader.shared.image(for: logoURL)
if let bild = await ArtworkLoader.shared.image(for: logoURL) {
loadedLogo = (logoURL, bild)
}
}
}