Und dein Argument dafür war das beste: auf dem Mac kann ich selbst hinsehen. Kein Simulator-Tunnel, der mitten im Prüfen abstürzt — `screencapture` auf das Fenster der App, und zwar nur auf ihres, nicht auf den Bildschirm. **Ein eigenes Ziel, nicht ein gemeinsames.** Die Info.plist der iOS-App ist voller iOS-Angaben: Startbildschirm, erlaubte Ausrichtungen, Live Activities. Die alle bedingt zu machen hieße, dass jede spätere Änderung an der iOS-Seite über eine Stelle geht, die auch den Mac betrifft. Zwei Ziele sind ein paar Zeilen mehr und eine Sorge weniger. **Was den Unterschied ausmacht, steht an einer Stelle.** Ein neuer `PlatformShims` in FlixyDesign übersetzt, was auf beiden Seiten verschieden heißt: schmale Titelzeile, halbhohes Blatt, Eingabefelder ohne Großschreibung, die Plätze in der Werkzeugleiste, die Zwischenablage. Zwanzig Ansichten benutzen ihn, statt zwanzigmal `#if os(iOS)` zu tragen — sonst wandert die Fallunterscheidung in jede neue Datei und wird beim übernächsten Bildschirm vergessen. Wirklich verschieden sind nur fünf Stellen: die Bildfläche (`UIView` gegen `NSView`), der AirPlay-Knopf, die Ausrichtungssperre (auf dem Mac gibt es nichts zu sperren), der App-Delegat für Hintergrund-Downloads und das Anmeldefenster, das ein anderes Fenster als Anker braucht. **Das Karussell blättert jetzt mit einem einrastenden Streifen** statt mit `TabView(.page)` — den Seitenstil gibt es auf dem Mac nicht. Das ist nebenbei die bessere Lösung: das verschachtelte TabView hatte die Eigenschaft, beim Wechsel kurz zwei Seiten gleichzeitig zu zeigen. **Was auf dem Mac fehlt und warum:** VLC. Das Binärpaket ist auf iOS festgelegt; sein xcframework enthält zwar Mac-Teile, aber das ist eine eigene Runde. Solange fällt die Wiedergabe dort auf den Systemplayer zurück — MKV und DTS gehen also noch nicht. Live Activities gibt es auf dem Mac nicht, Bild-in- Bild startet von Hand statt beim Verlassen der App. Gesehen: die App startet, der Anmeldebildschirm steht, beide Wege sind da. Angemeldet habe ich mich auf dem Mac **nicht** — das ginge nur, indem ich einen Token in deinen Schlüsselbund schreibe, und dann fragt macOS dich beim ersten Zugriff nach dem Passwort. Das machst du mit einem Klick selbst. iOS baut unverändert, 263 Tests grün. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
90 lines
3.1 KiB
Swift
90 lines
3.1 KiB
Swift
#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 ins Querformat und hält es dort.
|
|
func lockLandscape() {
|
|
guard UIDevice.current.userInterfaceIdiom != .pad else { return }
|
|
apply(.landscape, rotatingTo: .landscape)
|
|
}
|
|
|
|
/// Gibt die Ausrichtung wieder frei und dreht zurück ins Hochformat.
|
|
func releaseLandscape() {
|
|
guard UIDevice.current.userInterfaceIdiom != .pad else { return }
|
|
apply(.portrait, rotatingTo: .portrait)
|
|
}
|
|
|
|
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()
|
|
scene.requestGeometryUpdate(.iOS(interfaceOrientations: target))
|
|
}
|
|
}
|
|
|
|
/// 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
|