Der Mac ist nicht immer Catalyst

Die Bedienung mit Zeiger und Tastatur hing an
`#if targetEnvironment(macCatalyst)`. Das ist in dem Bau, der tatsächlich
benutzt wird, **falsch**: „für iPad angepasst" ist buchstäblich der
iOS-Bau, der auf Apple Silicon startet, und dort ist die Abfrage nicht
erfüllt. Klick-zum-Anhalten, Leertaste und die Pfeiltasten waren damit
genau dort abgeschaltet, wo sie gebraucht werden.

Jetzt beantwortet `FlixyPlatform.isMac` die Frage zur Laufzeit und kennt
beide Wege. Die Ansicht bleibt dabei eine einzige: `focusable(_:)` nimmt
den Schalter entgegen, die Tastenhandler geben sonst `.ignored` zurück.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-09-15 20:40:43 +02:00
co-authored by Claude Opus 5
parent 9a4b6b1e06
commit 37a6c2648f
2 changed files with 52 additions and 26 deletions
+28 -26
View File
@@ -32,6 +32,8 @@ struct PlayerView: View {
@State private var hintTask: Task<Void, Never>? @State private var hintTask: Task<Void, Never>?
/// Ob die Tastatur bei diesem Bildschirm ankommt. Nur auf dem Mac gesetzt. /// Ob die Tastatur bei diesem Bildschirm ankommt. Nur auf dem Mac gesetzt.
@FocusState private var keyboardFocus: Bool @FocusState private var keyboardFocus: Bool
/// Ob Zeiger und Tastatur gelten. Einmal beim Start ermittelt.
private var isMac: Bool { FlixyPlatform.isMac }
/// Wann der Zeiger zuletzt die Bedienelemente geholt hat. /// Wann der Zeiger zuletzt die Bedienelemente geholt hat.
@State private var lastPointerReveal: Date = .distantPast @State private var lastPointerReveal: Date = .distantPast
@@ -124,36 +126,28 @@ struct PlayerView: View {
// //
// Dort ist beides selbstverständlich: Leertaste hält an, die Pfeile // Dort ist beides selbstverständlich: Leertaste hält an, die Pfeile
// springen, und die Bedienelemente kommen von allein, sobald sich der // springen, und die Bedienelemente kommen von allein, sobald sich der
// Zeiger regt. Auf dem iPhone gibt es keines von beidem, und // Zeiger regt. Auf dem iPhone gibt es keines von beidem.
// `focusable` legte dort nur einen Fokusrahmen über den Film. //
#if targetEnvironment(macCatalyst) // Zur Laufzeit entschieden, nicht beim Übersetzen: „für iPad
.focusable() // angepasst" ist der iOS-Bau und läuft trotzdem auf dem Mac. Ein
// `#if` hätte das Ganze genau dort abgeschaltet. `focusable(_:)`
// nimmt den Schalter entgegen, die Tastenhandler geben sonst
// `.ignored` zurück — so bleibt es **eine** Ansicht, und der Player
// wird nicht neu aufgebaut, nur weil die Frage vom Gerät abhängt.
.focusable(isMac)
.focusEffectDisabled() .focusEffectDisabled()
.focused($keyboardFocus) .focused($keyboardFocus)
// Von Hand gesetzt, weil sonst niemand den Fokus hat: der Player // Von Hand gesetzt, weil sonst niemand den Fokus hat: der Player
// besteht aus Knöpfen, und solange keiner angeklickt wurde, ginge // besteht aus Knöpfen, und solange keiner angeklickt wurde, ginge
// jeder Tastendruck ins Leere. // jeder Tastendruck ins Leere.
.onAppear { keyboardFocus = true } .onAppear { if isMac { keyboardFocus = true } }
.onKeyPress(.space) { .onKeyPress(.space) { onMac { model.togglePlayPause() } }
model.togglePlayPause() .onKeyPress(.leftArrow) { onMac { seek(by: -Double(settings.rewindSeconds)) } }
revealControls() .onKeyPress(.rightArrow) { onMac { seek(by: Double(settings.forwardSeconds)) } }
return .handled
}
.onKeyPress(.leftArrow) {
seek(by: -Double(settings.rewindSeconds))
revealControls()
return .handled
}
.onKeyPress(.rightArrow) {
seek(by: Double(settings.forwardSeconds))
revealControls()
return .handled
}
.onContinuousHover { phase in .onContinuousHover { phase in
guard case .active = phase else { return } guard isMac, case .active = phase else { return }
pointerMoved() pointerMoved()
} }
#endif
.sheet(item: $trackPicker) { kind in .sheet(item: $trackPicker) { kind in
TrackPickerSheet(model: model, kind: kind) TrackPickerSheet(model: model, kind: kind)
} }
@@ -602,12 +596,20 @@ struct PlayerView: View {
/// ein Tipp, der sofort anhält, nähme den einzigen Weg zu Scrubber, /// ein Tipp, der sofort anhält, nähme den einzigen Weg zu Scrubber,
/// Tonspur und Qualität. /// Tonspur und Qualität.
private func primaryTap() { private func primaryTap() {
#if targetEnvironment(macCatalyst) guard isMac else {
toggleControls()
return
}
model.togglePlayPause() model.togglePlayPause()
revealControls() revealControls()
#else }
toggleControls()
#endif /// Führt eine Tastenaktion aus — aber nur dort, wo es eine Tastatur gibt.
private func onMac(_ action: () -> Void) -> KeyPress.Result {
guard isMac else { return .ignored }
action()
revealControls()
return .handled
} }
private func toggleControls() { private func toggleControls() {
@@ -12,6 +12,30 @@ import AppKit
/// Systeme verschiedene Namen dafür haben, steht der Unterschied hier und /// Systeme verschiedene Namen dafür haben, steht der Unterschied hier und
/// nirgends sonst. Sonst wandert er in zwanzig Dateien und wird beim /// nirgends sonst. Sonst wandert er in zwanzig Dateien und wird beim
/// nächsten Bildschirm vergessen. /// nächsten Bildschirm vergessen.
/// Auf was für einem Gerät läuft das hier gerade?
public enum FlixyPlatform {
/// Läuft die App auf einem Mac?
///
/// **Zwei Wege führen dorthin, und für den Übersetzer sehen sie
/// verschieden aus.** Catalyst ist ein eigener Bau und meldet sich als
/// `targetEnvironment(macCatalyst)`. „Für iPad angepasst" ist dagegen
/// buchstäblich der iOS-Bau, der auf Apple Silicon startet — dort ist
/// dieselbe Abfrage schlicht falsch.
///
/// Wer nur die erste Hälfte prüft, schreibt Code für den Mac, der auf dem
/// Mac nicht läuft: übersetzt sauber, ist aber genau dort abgeschaltet,
/// wo er gebraucht wird. Deshalb hier beide Wege, an einer Stelle.
public static let isMac: Bool = {
#if targetEnvironment(macCatalyst)
true
#elseif os(iOS)
ProcessInfo.processInfo.isiOSAppOnMac
#else
true
#endif
}()
}
extension View { extension View {
/// Eine schmale Titelzeile — auf dem Mac gibt es sie nicht. /// Eine schmale Titelzeile — auf dem Mac gibt es sie nicht.
public func flixyInlineTitle() -> some View { public func flixyInlineTitle() -> some View {