#if os(iOS) import SwiftUI import UIKit /// Bestimmt, in welche Richtungen sich die App drehen darf. /// /// In reinem SwiftUI gibt es dafür keinen Weg: `requestGeometryUpdate` dreht /// zwar, aber das System dreht sofort zurück, wenn der Bildschirm es nicht /// auch erlaubt. Erlaubt wird es über /// `application(_:supportedInterfaceOrientationsFor:)` — und das gibt es nur /// am klassischen App-Delegaten. Diese Klasse ist die Brücke dazwischen. @MainActor final class OrientationLock { static let shared = OrientationLock() private(set) var mask: UIInterfaceOrientationMask = .portrait private init() { // Auf dem iPad bleibt alles erlaubt: dort ist das Querformat beim // Blättern der Normalfall, nicht die Ausnahme. if UIDevice.current.userInterfaceIdiom == .pad { mask = .all } } /// Dreht zum Start der Wiedergabe ins Querformat — und gibt danach frei. /// /// **Gedreht wird einmal, gesperrt wird nicht.** Vorher blieb die Maske /// auf `.landscape` stehen, und damit liess sich im Player nicht mehr ins /// Hochformat drehen. Ein Film beginnt quer, weil er quer ist; wer danach /// im Bett auf der Seite liegt, soll drehen dürfen. func lockLandscape() { guard UIDevice.current.userInterfaceIdiom == .phone else { return } apply(.allButUpsideDown, rotatingTo: .landscape) } /// Gibt die Ausrichtung wieder frei und dreht zurück ins Hochformat. func releaseLandscape() { guard UIDevice.current.userInterfaceIdiom == .phone else { return } apply(.portrait, rotatingTo: .portrait) } /// Dreht auf Knopfdruck, ohne die Freiheit danach zu nehmen. func rotate(to target: UIInterfaceOrientationMask) { guard UIDevice.current.userInterfaceIdiom == .phone else { return } apply(.allButUpsideDown, rotatingTo: target) } private func apply( _ newMask: UIInterfaceOrientationMask, rotatingTo target: UIInterfaceOrientationMask ) { mask = newMask guard let scene = UIApplication.shared.connectedScenes .compactMap({ $0 as? UIWindowScene }) .first(where: { $0.activationState == .foregroundActive }) else { return } // Erst die neue Erlaubnis bekanntgeben, dann drehen. In der anderen // Reihenfolge lehnt das System die Drehung ab, weil die alte Maske // noch gilt — und das sah aus wie „dreht sich nicht richtig". scene.keyWindow?.rootViewController?.setNeedsUpdateOfSupportedInterfaceOrientations() // **Mit Fehlerbehandler.** Bei eingeschalteter Ausrichtungssperre // lehnt das System die Drehung ab. Ohne diesen Block wirft UIKit die // Ablehnung in die Konsole und es sieht aus, als hinge die App — // dabei ist es eine Einstellung des Nutzers und in Ordnung. Der Film // läuft dann eben hochkant weiter. scene.requestGeometryUpdate(.iOS(interfaceOrientations: target)) { fehler in #if DEBUG print("Drehung abgelehnt (vermutlich Ausrichtungssperre): \(fehler)") #endif } } } /// Dreht beim Erscheinen ins Querformat und beim Verschwinden zurück. struct LandscapeWhileVisible: ViewModifier { /// Ob überhaupt gesperrt wird. /// /// Für die allermeisten Filme ist das Querformat richtig — und für den, /// der im Bett auf der Seite liegt, das Gegenteil davon. let isEnabled: Bool func body(content: Content) -> some View { content .onAppear { if isEnabled { OrientationLock.shared.lockLandscape() } } .onDisappear { OrientationLock.shared.releaseLandscape() } } } extension View { /// Für den Vollbildplayer: Film ist Querformat. func landscapeWhileVisible(_ isEnabled: Bool = true) -> some View { modifier(LandscapeWhileVisible(isEnabled: isEnabled)) } } #else import SwiftUI /// Auf dem Mac dreht sich nichts. /// /// Ein Fenster hat keine Ausrichtung, die sich sperren ließe — der Nutzer /// bestimmt seine Größe. Die Stelle bleibt trotzdem bestehen, damit der /// Player auf beiden Plattformen dieselbe Zeile schreiben kann. extension View { func landscapeWhileVisible(_ isEnabled: Bool = true) -> some View { self } } #endif