From 70a5375b7ee6e98dfd170f7e47937a7ea5568242 Mon Sep 17 00:00:00 2001 From: Arkadiusz Fal Date: Thu, 16 Apr 2026 07:32:13 +0200 Subject: [PATCH] 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. --- Yattee/Views/Home/ContinueWatchingView.swift | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/Yattee/Views/Home/ContinueWatchingView.swift b/Yattee/Views/Home/ContinueWatchingView.swift index 20871654..deffb169 100644 --- a/Yattee/Views/Home/ContinueWatchingView.swift +++ b/Yattee/Views/Home/ContinueWatchingView.swift @@ -10,6 +10,9 @@ import SwiftUI struct ContinueWatchingView: View { @Environment(\.appEnvironment) private var appEnvironment @Namespace private var sheetTransition + #if os(tvOS) + @FocusState private var focusedVideoID: String? + #endif @State private var watchHistory: [WatchEntry] = [] // View options (persisted) @@ -149,6 +152,16 @@ struct ContinueWatchingView: View { .onReceive(NotificationCenter.default.publisher(for: .watchHistoryDidChange)) { _ in 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 @@ -176,7 +189,9 @@ struct ContinueWatchingView: View { ) { entryRowView(entry: entry, index: index) } - #if !os(tvOS) + #if os(tvOS) + .focused($focusedVideoID, equals: entry.videoID) + #else .videoSwipeActions( video: entry.toVideo(), fixedActions: [ @@ -235,6 +250,9 @@ struct ContinueWatchingView: View { TappableContinueWatchingGridCard(entry: entry, onRemove: { removeEntry(entry) }) + #if os(tvOS) + .focused($focusedVideoID, equals: entry.videoID) + #endif } } }