Focus first video by default in Continue Watching on tvOS

Use @FocusState with programmatic assignment on appear instead of
prefersDefaultFocus, which is broken when the target sits inside a
ScrollView/LazyVGrid on tvOS. A 0.15s delay gives the grid time to
materialize cells before the focus write.
This commit is contained in:
Arkadiusz Fal
2026-04-16 07:32:13 +02:00
parent 65724ae201
commit 70a5375b7e

View File

@@ -10,6 +10,9 @@ import SwiftUI
struct ContinueWatchingView: View { struct ContinueWatchingView: View {
@Environment(\.appEnvironment) private var appEnvironment @Environment(\.appEnvironment) private var appEnvironment
@Namespace private var sheetTransition @Namespace private var sheetTransition
#if os(tvOS)
@FocusState private var focusedVideoID: String?
#endif
@State private var watchHistory: [WatchEntry] = [] @State private var watchHistory: [WatchEntry] = []
// View options (persisted) // View options (persisted)
@@ -149,6 +152,16 @@ struct ContinueWatchingView: View {
.onReceive(NotificationCenter.default.publisher(for: .watchHistoryDidChange)) { _ in .onReceive(NotificationCenter.default.publisher(for: .watchHistoryDidChange)) { _ in
loadHistory() loadHistory()
} }
#if os(tvOS)
.onChange(of: inProgressEntries.first?.videoID, initial: true) { _, newValue in
// Work around tvOS ScrollView + prefersDefaultFocus bug: set initial focus
// to the first video after LazyVGrid has time to materialize cells.
guard let newValue else { return }
DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) {
focusedVideoID = newValue
}
}
#endif
} }
// MARK: - Content // MARK: - Content
@@ -176,7 +189,9 @@ struct ContinueWatchingView: View {
) { ) {
entryRowView(entry: entry, index: index) entryRowView(entry: entry, index: index)
} }
#if !os(tvOS) #if os(tvOS)
.focused($focusedVideoID, equals: entry.videoID)
#else
.videoSwipeActions( .videoSwipeActions(
video: entry.toVideo(), video: entry.toVideo(),
fixedActions: [ fixedActions: [
@@ -235,6 +250,9 @@ struct ContinueWatchingView: View {
TappableContinueWatchingGridCard(entry: entry, onRemove: { TappableContinueWatchingGridCard(entry: entry, onRemove: {
removeEntry(entry) removeEntry(entry)
}) })
#if os(tvOS)
.focused($focusedVideoID, equals: entry.videoID)
#endif
} }
} }
} }