Fix iOS playback settings menu text disappearing and resizing issues

When tapping menus in playback settings (playback mode, quality profile,
stream, rate, captions, audio track), the selected value text would
disappear and cause unwanted resizing animations.

Implemented ZStack overlay technique for all iOS menu buttons:
- Visible static label remains on screen
- Invisible Menu overlay (.opacity(0)) handles tap interactions
- Prevents text from disappearing when menu opens
- Eliminates resizing animations on option selection
This commit is contained in:
Arkadiusz Fal
2025-11-23 14:09:14 +01:00
parent 0c4609bcf1
commit 65e86d30ec

View File

@@ -209,16 +209,22 @@ struct PlaybackSettings: View {
.controlSize(.large)
.frame(width: 100, alignment: .center)
#elseif os(iOS)
ZStack {
Text(player.rateLabel(player.currentRate))
.foregroundColor(.primary)
.frame(width: 70)
Menu {
ratePicker
} label: {
Text(player.rateLabel(player.currentRate))
.foregroundColor(.primary)
.frame(width: 70)
.opacity(0)
}
.transaction { t in t.animation = .none }
.buttonStyle(.plain)
.foregroundColor(.primary)
.transaction { t in t.animation = .none }
}
.frame(width: 70, height: 40)
#else
Text(player.rateLabel(player.currentRate))
@@ -331,12 +337,20 @@ struct PlaybackSettings: View {
.controlSize(.large)
.frame(width: 300, alignment: .trailing)
#else
ZStack {
Label(player.playbackMode.description.localized(), systemImage: player.playbackMode.systemImage)
.foregroundColor(.accentColor)
Menu {
playbackModePicker
} label: {
Label(player.playbackMode.description.localized(), systemImage: player.playbackMode.systemImage)
.foregroundColor(.accentColor)
.opacity(0)
}
.buttonStyle(.plain)
.transaction { t in t.animation = .none }
}
#endif
}
@@ -356,16 +370,23 @@ struct PlaybackSettings: View {
.controlSize(.large)
.frame(width: 300, alignment: .trailing)
#elseif os(iOS)
ZStack {
Text(player.qualityProfileSelection?.description ?? "Automatic".localized())
.foregroundColor(.accentColor)
.frame(maxWidth: 240, alignment: .trailing)
Menu {
qualityProfilePicker
} label: {
Text(player.qualityProfileSelection?.description ?? "Automatic".localized())
.frame(maxWidth: 240, alignment: .trailing)
}
.transaction { t in t.animation = .none }
.buttonStyle(.plain)
.foregroundColor(.accentColor)
.frame(maxWidth: 240, alignment: .trailing)
.opacity(0)
}
.buttonStyle(.plain)
.transaction { t in t.animation = .none }
}
.frame(maxWidth: 240, alignment: .trailing)
.frame(height: 40)
#else
ControlsOverlayButton(focusedField: $focusedField, field: .qualityProfile) {
@@ -406,15 +427,22 @@ struct PlaybackSettings: View {
.controlSize(.large)
.frame(width: 300, alignment: .trailing)
#elseif os(iOS)
ZStack {
Text(player.streamSelection?.resolutionAndFormat ?? "loading...")
.foregroundColor(player.streamSelection == nil ? .secondary : .accentColor)
.frame(width: 140, height: 40, alignment: .trailing)
Menu {
StreamControl()
} label: {
Text(player.streamSelection?.resolutionAndFormat ?? "loading...")
.frame(width: 140, height: 40, alignment: .trailing)
.foregroundColor(player.streamSelection == nil ? .secondary : .accentColor)
.frame(width: 140, height: 40, alignment: .trailing)
.opacity(0)
}
.transaction { t in t.animation = .none }
.buttonStyle(.plain)
.transaction { t in t.animation = .none }
}
.frame(width: 140, height: 40, alignment: .trailing)
#else
StreamControl(focusedField: $focusedField)
@@ -429,6 +457,25 @@ struct PlaybackSettings: View {
.controlSize(.large)
.frame(width: 300, alignment: .trailing)
#elseif os(iOS)
ZStack {
HStack(spacing: 4) {
Image(systemName: "text.bubble")
if let captions = player.captions,
let language = LanguageCodes(rawValue: captions.code)
{
Text("\(language.description.capitalized) (\(language.rawValue))")
} else {
if videoCaptions?.isEmpty == true {
Text("Not available")
} else {
Text("Disabled")
}
}
}
.foregroundColor(.accentColor)
.frame(alignment: .trailing)
.frame(height: 40)
Menu {
if videoCaptions?.isEmpty == false {
captionsPicker
@@ -440,7 +487,6 @@ struct PlaybackSettings: View {
let language = LanguageCodes(rawValue: captions.code)
{
Text("\(language.description.capitalized) (\(language.rawValue))")
.foregroundColor(.accentColor)
} else {
if videoCaptions?.isEmpty == true {
Text("Not available")
@@ -449,13 +495,15 @@ struct PlaybackSettings: View {
}
}
}
.foregroundColor(.accentColor)
.frame(alignment: .trailing)
.frame(height: 40)
.opacity(0)
.disabled(videoCaptions?.isEmpty == true)
}
.transaction { t in t.animation = .none }
.buttonStyle(.plain)
.foregroundColor(.accentColor)
.transaction { t in t.animation = .none }
}
#else
ControlsOverlayButton(focusedField: $focusedField, field: .captions) {
HStack(spacing: 8) {
@@ -500,16 +548,23 @@ struct PlaybackSettings: View {
.controlSize(.large)
.frame(width: 300, alignment: .trailing)
#elseif os(iOS)
ZStack {
Text(player.selectedAudioTrack?.displayLanguage ?? "Original")
.foregroundColor(.accentColor)
.frame(maxWidth: 240, alignment: .trailing)
Menu {
audioTrackPicker
} label: {
Text(player.selectedAudioTrack?.displayLanguage ?? "Original")
.frame(maxWidth: 240, alignment: .trailing)
}
.transaction { t in t.animation = .none }
.buttonStyle(.plain)
.foregroundColor(.accentColor)
.frame(maxWidth: 240, alignment: .trailing)
.opacity(0)
}
.buttonStyle(.plain)
.transaction { t in t.animation = .none }
}
.frame(maxWidth: 240, alignment: .trailing)
.frame(height: 40)
#else
ControlsOverlayButton(focusedField: $focusedField, field: .audioTrack) {