在某些情况下,在 tvOS 上,集合视图会将其焦点重置到
reloadData()
。下面是重现问题的最少代码。
import SwiftUI
@main
struct DemosApp_tvOS: App {
var body: some Scene {
WindowGroup {
HomeView()
}
}
}
public struct HomeView: UIViewControllerRepresentable {
public func makeUIViewController(context: Context) -> HomeViewController { .init() }
public func updateUIViewController(_ uiViewController: HomeViewController, context: Context) { }
}
public final class HomeViewController: UIViewController {
public override func viewDidAppear(_ animated: Bool) {
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
self.present(CollectionViewController(collectionViewLayout: UICollectionViewFlowLayout()), animated: true)
}
}
}
public final class CollectionViewController: UICollectionViewController {
public override func viewDidLoad() {
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
}
public override func pressesEnded(_ presses: Set<UIPress>, with event: UIPressesEvent?) {
super.pressesEnded(presses, with: event)
if presses.contains(where: { $0.type == .playPause }) {
collectionView.reloadData()
}
}
public override func numberOfSections(in collectionView: UICollectionView) -> Int { 1 }
public override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 7 }
public override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)
cell.backgroundColor = .gray
cell.layer.borderColor = UIColor.blue.cgColor
return cell
}
public override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
present(UIViewController(), animated: true)
}
public override func didUpdateFocus(in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
context.previouslyFocusedView?.layer.borderWidth = 0
context.nextFocusedView?.layer.borderWidth = 4
}
}
基本上需要满足以下条件:
reloadData
调用(在上面的示例中,按下远程播放/暂停按钮将重新加载集合视图)。在
reloadData()
调用发生之前,焦点正确地保留在所选项目上。
但是,一旦发生
reloadData()
调用,焦点就会重置到第一个单元格。这种情况仅在从细节 VC 返回后第一次发生。
后续数据重新加载不会重置焦点,唯一有问题的调用是详细信息 VC 被关闭后的第一次重新加载。
此外,如果集合视图 VC 不是以模态方式呈现在另一个 VC 上,则不会发生重新加载问题。
有人知道这个问题的解决方案或解决方法吗?一种不涉及重新设计 UI 层次结构的方法。
附注我尝试使用
shouldUpdateFocus(in:)
和 indexPathForPreferredFocusedView(in:)
委托方法,但没有成功。
我就此提交了一份 Apple 报告 (FB13262879),同时我搜索了解决方法,并找到了一个。
解决方法涉及设置
collectionView.remembersLastFocusedIndexPath = false
,以及“手动”管理集合视图焦点:
private var lastFocusedIndexPath: IndexPath?
public func collectionView(_ collectionView: UICollectionView, didUpdateFocusIn context: UICollectionViewFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
lastFocusedIndexPath = context.nextFocusedIndexPath
}
public func indexPathForPreferredFocusedView(in collectionView: UICollectionView) -> IndexPath? {
return lastFocusedIndexPath
}
最后一个难题是强制集合视图在错误重置焦点后恢复焦点:
let indexPathToRestoreFocus = lastFocusedIndexPath
collectionView.reloadData()
CATransaction.setCompletionBlock { [self] in
lastFocusedIndexPath = indexPathToRestoreFocus
collectionView.setNeedsFocusUpdate()
collectionView.updateFocusIfNeeded()
}
这不是一个很好的解决方法,并且将来可能会崩溃,希望 Apple 届时修复 UIKit 错误。