Eingepasst heisst ganz zu sehen — und der Rahmen wird mitgemessen

**Das Bild bleibt im sicheren Bereich, wenn es eingepasst ist.** Vorher
lief es bis an die Bildschirmkante und damit unter die Dynamic Island —
im Querformat frisst sie eine Ecke des Films. „Einpassen" und „ein Stück
fehlt" schliessen sich aus. Das kostet quer gut hundert Punkte Breite;
wem das zu klein ist, legt mit zwei Fingern auf **füllen** um, wo es
bewusst bis an die Kanten läuft und nur der Untertitel ausgespart bleibt.

**Und die Diagnosezeile misst jetzt den Rahmen mit.** Das ist die Frage,
an der alles hängt, und ich habe sie zweimal übersprungen: ist der Rahmen
grösser als der Bildschirm, liegt es nicht an den Rändern — dann wird der
Player in einen zu grossen Kasten gelegt und mittig darin ausgerichtet,
und oben wie unten fällt gleich viel heraus. Genau so sieht es aus, wenn
die Kopfzeile fehlt **und** die Zeitangaben fehlen, während die Knöpfe in
der Mitte stehen.

Der Verdacht, warum ich es nie nachstellen konnte: die Bildschirmfotos
sind 2796×1290, also ein Pro Max. Der ist im Querformat `regular` und
nimmt damit in `RootView` einen **anderen Zweig** — Seitenleiste statt
Leiste unten, mit `safeAreaInset` und `ignoresSafeArea(.container)`. Mein
Simulator ist ein 17 Pro und läuft quer über den anderen Zweig. Belegt
ist das noch nicht; die Zeile wird es sagen.

Im Simulator steht dort „Rahmen 874×402 @0,0 · Schirm 874×402" — gleich
gross, Ursprung in der Ecke, alles liegt drin.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-09-30 16:25:13 +02:00
co-authored by Claude Opus 5
parent c7b901b103
commit 7bcef03c25
+37 -5
View File
@@ -49,6 +49,16 @@ struct PlayerView: View {
/// genau der Moment des Drehens, und genau dann stimmen die alten Werte /// genau der Moment des Drehens, und genau dann stimmen die alten Werte
/// nicht mehr. /// nicht mehr.
@State private var safeArea = EdgeInsets() @State private var safeArea = EdgeInsets()
#if DEBUG
/// Wie gross der Player gezeichnet wird und wo er anfängt.
///
/// **Die Frage, an der alles hängt.** Ist der Rahmen grösser als der
/// Bildschirm, liegt es nicht an den Rändern — dann wird der Player in
/// einen zu grossen Kasten gelegt und mittig darin ausgerichtet, und
/// oben wie unten fällt gleich viel heraus. Genau so sieht es aus, wenn
/// die Kopfzeile fehlt und die Zeitangaben auch.
@State private var rahmen: CGRect = .zero
#endif
@State private var showsControls = true @State private var showsControls = true
@State private var trackPicker: TrackPickerSheet.Kind? @State private var trackPicker: TrackPickerSheet.Kind?
@State private var showsQualityChoice = false @State private var showsQualityChoice = false
@@ -241,6 +251,11 @@ struct PlayerView: View {
} action: { _ in } action: { _ in
safeArea = ScreenSafeArea.current safeArea = ScreenSafeArea.current
} }
#if DEBUG
.onGeometryChange(for: CGRect.self) { proxy in
proxy.frame(in: .global)
} action: { rahmen = $0 }
#endif
.onAppear { safeArea = ScreenSafeArea.current } .onAppear { safeArea = ScreenSafeArea.current }
// **Auch beim Zurückholen der App.** Eine Drehung im Hintergrund // **Auch beim Zurückholen der App.** Eine Drehung im Hintergrund
// ändert die Fläche, ohne dass `onGeometryChange` hier feuert — die // ändert die Fläche, ohne dass `onGeometryChange` hier feuert — die
@@ -404,7 +419,7 @@ struct PlayerView: View {
private func diagnose(for model: PlayerModel) -> some View { private func diagnose(for model: PlayerModel) -> some View {
#if DEBUG #if DEBUG
let schirm = ScreenSafeArea.screenSize let schirm = ScreenSafeArea.screenSize
Text(verbatim: "SA \(Int(safeArea.top)),\(Int(safeArea.leading)),\(Int(safeArea.trailing)),\(Int(safeArea.bottom)) Schirm \(Int(schirm.width))×\(Int(schirm.height)) \(model.videoFit == .fill ? "füllen" : "einpassen")") Text(verbatim: "SA \(Int(safeArea.top)),\(Int(safeArea.leading)),\(Int(safeArea.trailing)),\(Int(safeArea.bottom)) Rahmen \(Int(rahmen.width))×\(Int(rahmen.height)) @\(Int(rahmen.minX)),\(Int(rahmen.minY)) Schirm \(Int(schirm.width))×\(Int(schirm.height)) \(model.videoFit == .fill ? "füllen" : "einpassen")")
.font(.system(size: 11).monospaced()) .font(.system(size: 11).monospaced())
.foregroundStyle(.yellow) .foregroundStyle(.yellow)
#endif #endif
@@ -867,9 +882,9 @@ struct PlayerView: View {
engine: model.engine, engine: model.engine,
surface: model.surface, surface: model.surface,
isActive: playback.presentation == .full, isActive: playback.presentation == .full,
// **Nur im Füllmodus.** Eingepasst liegen unten ohnehin schwarze // **Nur im Füllmodus.** Eingepasst liegt das Bild seit
// Balken, in denen der Untertitel Platz hat; dort etwas // Neuestem ohnehin im sicheren Bereich — dort kann kein
// auszusparen nähme Bild, ohne etwas zu retten. // Untertitel mehr unter die Kante geraten.
bottomInset: model.videoFit == .fill ? safeArea.bottom : 0 bottomInset: model.videoFit == .fill ? safeArea.bottom : 0
) )
@@ -882,7 +897,24 @@ struct PlayerView: View {
} else if let breite = model.statistics?.width, } else if let breite = model.statistics?.width,
let höhe = model.statistics?.height, let höhe = model.statistics?.height,
breite > 0, höhe > 0 { breite > 0, höhe > 0 {
fläche.aspectRatio(CGFloat(breite) / CGFloat(höhe), contentMode: .fit) // **Eingepasst heisst: ganz zu sehen.**
//
// Vorher lief das Bild bis an die Bildschirmkante und damit unter
// die Dynamic Island — im Querformat frisst sie eine Ecke des
// Films. „Einpassen" und „ein Stück fehlt" schliessen sich aus;
// wer das ganze Bild will, soll es bekommen.
//
// Das kostet im Querformat gut hundert Punkte Breite. Wem das
// Bild dann zu klein ist, legt mit zwei Fingern auf **füllen**
// um — dort läuft es bewusst bis an die Kanten, und nur der
// Untertitel bleibt ausgespart.
//
// Das Polster steht **nach** dem Seitenverhältnis: so bekommt das
// Einpassen die verkleinerte Fläche vorgeschlagen und rechnet
// damit, statt hinterher beschnitten zu werden.
fläche
.aspectRatio(CGFloat(breite) / CGFloat(höhe), contentMode: .fit)
.padding(safeArea)
} else { } else {
fläche fläche
} }