diff --git a/Flixy/Views/Player/ScreenSafeArea.swift b/Flixy/Session/ScreenSafeArea.swift similarity index 92% rename from Flixy/Views/Player/ScreenSafeArea.swift rename to Flixy/Session/ScreenSafeArea.swift index 5801031..0890bc1 100644 --- a/Flixy/Views/Player/ScreenSafeArea.swift +++ b/Flixy/Session/ScreenSafeArea.swift @@ -47,13 +47,6 @@ enum ScreenSafeArea { ) } - /// 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. diff --git a/Flixy/Views/Navigation/FlixyNavigationBar.swift b/Flixy/Views/Navigation/FlixyNavigationBar.swift index 3577204..fbb85a4 100644 --- a/Flixy/Views/Navigation/FlixyNavigationBar.swift +++ b/Flixy/Views/Navigation/FlixyNavigationBar.swift @@ -105,9 +105,25 @@ struct FlixySidebar: View { VStack(alignment: .leading, spacing: 0) { header - // Ohne Bildlaufansicht im Ruhezustand: die Einträge sind wenige - // und passen. Beim Bearbeiten braucht es eine Liste — nur die - // bringt das Ziehen zum Umsortieren mit. + // **Doch mit Bildlaufansicht.** Hier stand „die Einträge sind + // wenige und passen" — und das stimmte auf einem iPad und im + // Hochformat. Auf einem grossen iPhone **quer** ist der + // Bildschirm vierhundertvierzig Punkte hoch, und die Leiste + // braucht mit Kopf, Gruppen, Trenner und Kontozeile gut + // fünfhundertdreissig. + // + // Eine `VStack` schrumpft dann nicht, sie **überragt**: der + // `HStack` daneben nimmt das Maximum, und der ganze Wurzelbaum + // wurde hundertachtunddreissig Punkte höher als der Bildschirm. + // Was darin als Überlagerung liegt — der Player — ging oben und + // unten hinaus, und Kopfzeile wie Zeitangaben waren weg. Der + // Fehler sass also in der Navigation und zeigte sich im Player. + // + // `basedOnSize`: es wird nur gescrollt, wenn es nötig ist. Wo die + // Einträge passen, sieht man keinen Unterschied. + // + // Beim Bearbeiten braucht es eine Liste — nur die bringt das + // Ziehen zum Umsortieren mit, und die scrollt von sich aus. if isEditing { Text("sidebar.editHint") .font(.flixyCaption) @@ -137,13 +153,17 @@ struct FlixySidebar: View { .scrollContentBackground(.hidden) .environment(\.editMode, .constant(.active)) } else { - VStack(alignment: .leading, spacing: FlixySpacing.regular) { - ForEach(groups) { group in - section(visible(group)) + ScrollView { + VStack(alignment: .leading, spacing: FlixySpacing.regular) { + ForEach(groups) { group in + section(visible(group)) + } } + .padding(.horizontal, FlixySpacing.snug) + .padding(.bottom, FlixySpacing.regular) } - .padding(.horizontal, FlixySpacing.snug) - .padding(.bottom, FlixySpacing.regular) + .scrollBounceBehavior(.basedOnSize) + .scrollIndicators(.never) } Spacer(minLength: 0) diff --git a/Flixy/Views/Navigation/RootView.swift b/Flixy/Views/Navigation/RootView.swift index 3395ac1..3060304 100644 --- a/Flixy/Views/Navigation/RootView.swift +++ b/Flixy/Views/Navigation/RootView.swift @@ -113,12 +113,31 @@ struct RootView: View { } .background(Color.flixyCanvas) .environment(topChrome) - // Hier und nicht weiter innen: ab dem `HStack` wird der obere - // Sicherheitsrand ignoriert, damit der Inhalt bis an den - // Bildschirmrand reicht. Wer danach misst, bekommt null. - .onGeometryChange(for: CGFloat.self) { proxy in - proxy.safeAreaInsets.top - } action: { statusBarHeight = $0 } + // **Aus dem Fenster, nicht aus dem Layout — und das ist der ganze + // Punkt.** + // + // Hier stand `proxy.safeAreaInsets.top`, und daraus wurde eine + // Rückkopplung: der gemessene Rand geht in `topInset`, `topInset` + // geht in einen `safeAreaInset` am Inhalt, und der verändert genau + // den Rand, der gerade gemessen wurde. Das Layout lief danach in + // Runden auf die richtige Grösse zu — jede Runde zweiundsechzig + // Punkte, also genau eine Statusleiste — und blieb auf halbem Weg + // stehen, weil SwiftUI irgendwann aufhört, neu zu rechnen. + // + // Sichtbar war das nur im Querformat auf einem grossen iPhone: dort + // ist die Anordnung `regular`, nur dort gibt es den `safeAreaInset`, + // und nur dort ist der Rand vorher und nachher verschieden. Der + // Wurzelbaum war dadurch hundertachtzehn Punkte höher als der + // Bildschirm und begann neunundsechzig darüber — und der Player, der + // als Überlagerung darin liegt, ging oben und unten hinaus. Genau so + // verschwanden Kopfzeile und Zeitangaben. + // + // Das Fenster kennt seinen Rand ohne Rückkopplung. Die Grösse bleibt + // der Anlass zum Nachfragen, der **Wert** kommt von aussen. + .onGeometryChange(for: CGSize.self) { proxy in + proxy.size + } action: { _ in statusBarHeight = ScreenSafeArea.current.top } + .onAppear { statusBarHeight = ScreenSafeArea.current.top } // Hier statt in der Scene: eine View wertet Beobachtungsänderungen // zuverlässig neu aus, ein Scene-Body nicht. .flixyAccent(theme) diff --git a/Flixy/Views/Player/PlayerView.swift b/Flixy/Views/Player/PlayerView.swift index 153f69e..9d20b67 100644 --- a/Flixy/Views/Player/PlayerView.swift +++ b/Flixy/Views/Player/PlayerView.swift @@ -49,16 +49,6 @@ struct PlayerView: View { /// genau der Moment des Drehens, und genau dann stimmen die alten Werte /// nicht mehr. @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 trackPicker: TrackPickerSheet.Kind? @State private var showsQualityChoice = false @@ -251,11 +241,6 @@ struct PlayerView: View { } action: { _ in safeArea = ScreenSafeArea.current } - #if DEBUG - .onGeometryChange(for: CGRect.self) { proxy in - proxy.frame(in: .global) - } action: { rahmen = $0 } - #endif .onAppear { safeArea = ScreenSafeArea.current } // **Auch beim Zurückholen der App.** Eine Drehung im Hintergrund // ändert die Fläche, ohne dass `onGeometryChange` hier feuert — die @@ -379,7 +364,6 @@ struct PlayerView: View { transport(for: model) Spacer() scrubber(for: model) - diagnose(for: model) } .padding(FlixySpacing.loose) // **Zusätzlich zum Polster, nicht statt.** Der Sicherheitsrand sagt, @@ -404,27 +388,6 @@ struct PlayerView: View { } } - /// Was der Player über seinen Platz weiss. - /// - /// **Nur im Entwicklerbau, und nur, weil zweimal Raten nicht gereicht - /// hat.** Sitzen die Knöpfe ausserhalb des Bildschirms, 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. Diese Zeile unterscheidet sie. - /// - /// Die Schirmgrösse daneben ist dabei der Schlüssel: steht dort etwas - /// anderes als die Fläche, auf der die Knöpfe liegen, liegt es nicht an - /// den Rändern. - @ViewBuilder - private func diagnose(for model: PlayerModel) -> some View { - #if DEBUG - let schirm = ScreenSafeArea.screenSize - 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()) - .foregroundStyle(.yellow) - #endif - } - private func topBar(for model: PlayerModel) -> some View { HStack(alignment: .top, spacing: FlixySpacing.regular) { // Der Pfeil verkleinert, er beendet nicht. Das ist der ganze