Files
Flaro/Flixy/Views/Player/ScreenSafeArea.swift
T
ScarriffleandClaude Opus 5 c7b901b103 Der Player sagt jetzt, was er über seinen Platz weiss
Zweimal habe ich behauptet, die Ränder seien behoben, und zweimal sassen
die Knöpfe weiter ausserhalb. Im Simulator steht dort „SA 0,62,62,20 ·
Schirm 874×402" und alles liegt drin — nachstellen lässt es sich hier
also nicht. Damit ist Raten der falsche Weg.

**Eine Zeile im Entwicklerbau**, unter dem Schieberegler: die Ränder, wie
sie ankommen, die Bildschirmgrösse daneben und der Skalierungsmodus.
Sitzen die Knöpfe daneben, gibt es genau drei Möglichkeiten — die Ränder
kommen als null an, sie kommen falsch an, oder sie stimmen und der Rahmen
ist grösser als der Schirm. Von aussen sehen alle drei gleich aus. Die
Zeile unterscheidet sie.

**Und eine Härtung, die beim Nachlesen auffiel.** `ScreenSafeArea` suchte
nur die Szene, die `foregroundActive` ist. Genau die gibt es in zwei
Momenten nicht: während einer Drehung und kurz nach dem Zurückholen der
App. Wer dann fragt, bekommt `nil`, daraus werden Ränder von null — und
die bleiben stehen, bis sich die Fläche wieder ändert. Die Knöpfe sitzen
dann dauerhaft im Home-Indikator, obwohl die Rechnung stimmt.

Ein Fenster mit den richtigen Rändern ist in dem Moment trotzdem da, es
ist nur nicht „aktiv". Jetzt der Reihe nach: aktiv, sonst sichtbar, sonst
irgendeines. Dazu wird beim Zurückholen der App nachgefasst — eine
Drehung im Hintergrund ändert die Fläche, ohne dass die Ansicht davon
etwas merkt.

Ob das der Fehler war, weiss ich nicht. Die Zeile wird es sagen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-30 15:59:04 +02:00

80 lines
3.8 KiB
Swift

import SwiftUI
import UIKit
/// Wo der Bildschirm aufhört — für die Ansichten, die absichtlich darüber
/// hinauslaufen.
///
/// **Warum nicht aus SwiftUI.** Der Sicherheitsrand ist in SwiftUI kein Wert,
/// den man erfragt, sondern ein Rand, den man verbraucht: wer ihn respektiert,
/// sieht ihn als null, und wer ihn mit `ignoresSafeArea` überschreitet, sieht
/// ihn danach ebenfalls als null. Ein `GeometryReader` dazwischenzuschieben
/// hilft nicht, er nimmt der Bildfläche das `ignoresSafeArea` gleich mit — und
/// dann ist das Bild seitlich um die Notch-Breite kleiner.
///
/// Der Versuch, ihn eine Ebene höher zu messen und hineinzureichen, hat es
/// auch nicht behoben: gemessen in einer Ansicht, angewendet in einer anderen,
/// über eine Drehung hinweg. Auf dem Gerät kamen dabei die Werte der falschen
/// Ausrichtung an.
///
/// **Das Fenster weiß es immer.** Es kennt seine Ausrichtung, es kennt die
/// Notch, und es antwortet in dem Moment, in dem gefragt wird. Das ist die
/// einzige Stelle in dieser App, die so fragt — und genau die eine, an der es
/// keinen SwiftUI-Weg gibt.
///
/// Gebraucht wird es für die Untertitel im Füllmodus: die Bildebene reicht
/// dort über die untere Kante hinaus, und AVFoundation zeichnet die Schrift an
/// ihren unteren Rand.
@MainActor
enum ScreenSafeArea {
/// Was der Bildschirm gerade verdeckt — Notch, Dynamic Island,
/// Home-Indikator, gerundete Ecken.
///
/// **Jedes Mal frisch gefragt, nie gemerkt.** Beim Drehen ändert sich
/// jeder dieser Werte, und ein gemerkter wäre genau dann falsch, wenn er
/// gebraucht wird.
static var current: EdgeInsets {
let i = windowInsets
// `left`/`right` und nicht `leading`/`trailing`: UIKit denkt in
// Bildschirmseiten, SwiftUI in Leserichtung. Bei einer Sprache, die
// von rechts nach links läuft, wären sie vertauscht — deshalb hier
// ausdrücklich in die Leserichtung übersetzt.
let vonLinks = fenster?.effectiveUserInterfaceLayoutDirection != .rightToLeft
return EdgeInsets(
top: i.top,
leading: vonLinks ? i.left : i.right,
bottom: i.bottom,
trailing: vonLinks ? i.right : i.left
)
}
/// Wie gross der Bildschirm gerade ist.
///
/// Nur für die Diagnose: sie beantwortet die Frage, ob der Player grösser
/// gezeichnet wird als der Schirm — und die lässt sich von innen sonst
/// nicht stellen.
static var screenSize: CGSize { fenster?.bounds.size ?? .zero }
private static var windowInsets: UIEdgeInsets { fenster?.safeAreaInsets ?? .zero }
/// Das Fenster, das gerade zählt.
///
/// **Mit Rückfall, und das ist der Punkt.** Zuerst stand hier nur
/// „die Szene, die `foregroundActive` ist" — und genau die gibt es in
/// zwei Momenten nicht: während einer Drehung und kurz nach dem
/// Zurückholen der App. Wer dann fragt, bekommt `nil`, daraus werden
/// Ränder von null, und die bleiben stehen, bis sich die Fläche wieder
/// ändert. Die Knöpfe sitzen dann im Home-Indikator, und zwar dauerhaft,
/// obwohl die Rechnung stimmt.
///
/// Ein Fenster, das die richtigen Ränder kennt, ist in dem Moment
/// trotzdem da — es ist nur nicht „aktiv". Also der Reihe nach: aktiv,
/// sonst sichtbar, sonst irgendeines.
private static var fenster: UIWindow? {
let szenen = UIApplication.shared.connectedScenes.compactMap { $0 as? UIWindowScene }
let gewählt = szenen.first { $0.activationState == .foregroundActive }
?? szenen.first { $0.activationState == .foregroundInactive }
?? szenen.first
return gewählt?.keyWindow ?? gewählt?.windows.first { $0.isKeyWindow } ?? gewählt?.windows.first
}
}