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 ) } 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 } }