在 tableView.remembersLastFocusedIndexPath = false 時候,列表會出現失去焦點時候,自動往上滾動的問題。

在 tableView.remembersLastFocusedIndexPath = true 時候,會出現 SwiftUI 繪製的,無法更改 UITableView 的焦點問題。

remembersLastFocusedIndexPath = false

先來觀看 Bug 的效果

當設置為 tableView.remembersLastFocusedIndexPath = false 時候,焦點會自行往上移動

Simulator Screen Recording - tvOS 18.2 - 2025-01-13 at 04.11.21.mp4

@ViewBuilder func CocoaListView() -> some View {
        CocoaList(data.epsList) { ep in
            CocoaListEpView(ep)
        }
        .introspectTableView { tableView in
            self.tableView = tableView
            tableView.delegate = delegateHandler
            tableView.allowsFocus = true
            tableView.allowsSelection = true
            tableView.isPrefetchingEnabled = true
            tableView.remembersLastFocusedIndexPath = false // 導致焦點自動移動
            tableView.rowHeight = UITableView.automaticDimension
            tableView.estimatedRowHeight = 342
            tableView.contentInsetAdjustmentBehavior = .never
            tableView.sectionHeaderTopPadding = 0
        }

remembersLastFocusedIndexPath = true

當設置為 tableView.remembersLastFocusedIndexPath = true 時候,問題不存在。但是從 SwiftUI 區域進行外部焦點控制會失效。

Simulator Screen Recording - tvOS 18.2 - 2025-01-13 at 04.15.00.mp4

@ViewBuilder func CocoaListView() -> some View {
        CocoaList(data.epsList) { ep in
            CocoaListEpView(ep)
        }
        .introspectTableView { tableView in
            self.tableView = tableView
            tableView.delegate = delegateHandler
            tableView.allowsFocus = true
            tableView.allowsSelection = true
            tableView.isPrefetchingEnabled = true
            tableView.remembersLastFocusedIndexPath = true // 導致焦點自動移動
            tableView.rowHeight = UITableView.automaticDimension
            tableView.estimatedRowHeight = 342
            tableView.contentInsetAdjustmentBehavior = .never
            tableView.sectionHeaderTopPadding = 0
            
            if data.epsList.count > 3 {
                // Add footer view with BackTopBar
                let footerView = UIHostingController(rootView:
                    ListBackTopBar { scrollTo(ep: data.epsList.first) { } }
                )
                footerView.view.backgroundColor = .clear
                footerView.view.frame = CGRect(
                    x: 0,
                    y: 0,
                    width: tableView.frame.width,
                    height: appSetting.playlistIssueFocus ? 650 : 50
                )
                tableView.tableFooterView = footerView.view
            }
        }
//來自 SwiftUI 位置的
ButtonCard(symbol: "eyeglasses") {
		scrollTo(ep: lastWatchedEp) { }
}
func scrollTo(ep: UniversalEpisodes.ep?, completionHandler: @escaping () -> Void) {
        if data.epsList.isEmpty {
            print(#function, "list is empty")
            completionHandler()
            return
        }

        guard
            let ep = ep,
            let epIndex = data.epsList.firstIndex(of: ep)
        else {
            print(#function, "ep is nil")
            completionHandler()
            return
        }

        if delegateHandler.focusedIndex == epIndex {
            print(#function, "focusedIndex == epIndex")
            completionHandler()
            return
        }

        // Start ...

        var row: Int = epIndex

        if row >= data.epsList.count - 1 {
            row = data.epsList.count - 1
        }

        if row < 0 {
            row = 0
        }

        let indexPath = IndexPath(row: row, section: 0)

        DispatchQueue.main.async {
            tableView?.remembersLastFocusedIndexPath = false

            // 强制更新布局
            self.tableView?.setNeedsLayout()
            self.tableView?.layoutIfNeeded()

            tableView?.scrollToRow(at: indexPath, at: .top, animated: false)

            // 直接计算目标位置
            if let rect = tableView?.rectForRow(at: indexPath) {
                // 偏移列表到指定位置
                let targetOffset = CGPoint(x: 0, y: rect.origin.y)
                tableView?.setContentOffset(targetOffset, animated: false)
            }

            if focusedField != .equalsID("CocoaListEpView") {
                focusedField = .equalsID("CocoaListEpView")
            }

            DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
                tableView?.becomeFirstResponder()
                tableView?.setNeedsFocusUpdate()
                tableView?.updateConstraintsIfNeeded()

                if let cell = tableView?.cellForRow(at: indexPath) {
                    cell.setNeedsFocusUpdate()
                    cell.updateFocusIfNeeded()
                } else {
                    print(#function, "not found cell")
                }

                print(#function, "callback", focusedField ?? "focus-nil")
                tableView?.remembersLastFocusedIndexPath = true
                completionHandler()
            }
        }
    }

Simulator Screenshot - tvOS 18.2 - 2025-01-13 at 04.17.01.jpeg

回到頂部 UIKit 內部的觸發按鈕

是可以正常使用 scrollTo 到列表頂部成員。

最後觀看(眼鏡 小圖標)在 SwiftUI 區域

是無法使用 ScrollTo 函數到達指定列表成員。

解決辦法