swift 在呈现另一个VC后重新加载数据时,UICollectionView重置焦点

6tqwzwtp  于 2023-10-15  发布在  Swift
关注(0)|答案(1)|浏览(99)

在某些情况下,在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
    }
}

基本上,需要满足以下条件:

  • 集合视图VC被呈现在另一个VC的顶部(例如,许多应用程序都有主屏幕)
  • 用户选择除第一项以外的任何项
  • 从集合视图中选择一个项目会显示另一个VC(让我们称之为细节VC)
  • 用户忽略细节VC
  • 在返回到集合视图屏幕之后,某些事件触发reloadData调用(在上面的示例中,按下远程播放/暂停按钮将重新加载集合视图)。

reloadData()调用发生之前,焦点正确地保持在所选项目上。
但是,一旦reloadData()调用发生,焦点就会重置到第一个单元格。而且这种情况只发生在从细节VC返回后的第一次。
随后的数据重载不会重置焦点,唯一有问题的调用是在细节VC被解散后的第一次重载。
此外,如果集合视图VC没有在另一个VC上以模态方式呈现,则不会发生重新加载问题。
有人知道解决这个问题的方法吗?一个不涉及重新设计UI层次结构的方法。
P.S.我试着玩弄shouldUpdateFocus(in:)indexPathForPreferredFocusedView(in:)委托方法,但没有成功。

h6my8fg2

h6my8fg21#

我就此提交了一份苹果报告(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()
}

这不是一个很好的解决办法,可能会在未来打破,希望苹果修复UIKit的错误。

相关问题