PAYATHON 2026

tvOS 26.3 嵌套列表焦点跳过第二分区,如何排查?

数据小陈

仅凭这条警告,无法确定 Section 2 被跳过的具体原因,也不能确认这是 tvOS 26.3 已被官方认定的缺陷。建议先检查外层 cell 的可聚焦设置、首选焦点关系,以及 Section 2 在回退瞬间的可见布局,再用最小示例判断是否属于系统回归。

already-visited container 警告说明了什么?

Ignoring attempt to add focus items in already-visited container. This can potentially cause infinite recursion.

从日志字面来看,焦点系统在一次相关遍历中遇到了已经访问过的容器,因此忽略了再次添加焦点项的操作,以避免潜在的无限递归。日志没有指出具体容器,也没有证明应用的视图层级存在循环。

可以从以下方向排查:

  • 自定义焦点关系是否形成环路,例如父容器优先指向子容器,子容器又指回父容器。
  • 焦点更新期间是否修改了布局、重载了数据或反复请求更新,使问题更难定位。
  • UIKit 是否在当前嵌套结构下出现了内部遍历异常。

这些情况都尚未确认。即使警告与跳过 Section 2 同时出现,也不一定有直接因果关系。

为什么向下正常,向上却跳过第二分区?

方向导航需要结合焦点项的位置和资格判断,section 的数据顺序本身不会强制焦点逐个经过。向上移动时,候选项的几何关系、遮挡情况、滚动位置和自定义限制可能与向下移动时不同。

需要检查 Section 2 在异常发生的那一刻:

  1. UICollectionView 是否仍在窗口中,是否有可见且可聚焦的 cell。
  2. 外层行高、内层 bounds 和布局属性是否正确,是否存在零高度或裁剪。
  3. 内层 cell 是否被其他视图遮挡;Apple 说明,完全遮挡可能影响焦点资格。UIFocusItem 文档
  4. canFocusItemAt、shouldUpdateFocus 及其 delegate 版本是否排除了目标。
  5. 复用后的 cell 是否仍保存旧 collection view、旧目标或失效的 IndexPath。

先让内层内容承担焦点

如果外层 UITableViewCell 只用于承载 UICollectionView,通常应将外层 cell 设为不可聚焦,让焦点进入内部内容。

在现有 UITableViewDelegate 中检查或添加:

func tableView(
    _ tableView: UITableView,
    canFocusRowAt indexPath: IndexPath
) -> Bool {
    // 适用于这些行都只承载内层可聚焦内容的情况。
    return false
}

Apple 文档明确指出,返回 false 只让该行自身不可聚焦,不会阻止其内部内容获得焦点。如果表格中还有需要直接聚焦的普通行,应按行类型分别返回。canFocusRowAt 文档

同时确认内层 UICollectionView 的 delegate 没有禁止对应 item 获得焦点。这是一项焦点配置检查,不能保证单独解决当前版本的异常。

检查首选焦点关系,避免指回祖先

如果重写了 preferredFocusEnvironments,应检查实际返回的对象,尤其要留意 cell 复用后的结果。对于当前结构,可以让承载 cell 优先指向自己的 collection view,再由 collection view 选择内部 item。避免让内部对象反向优先指向外层 table、父 cell 或控制器,以免形成循环首选关系。

Apple 说明,系统会递归搜索 preferredFocusEnvironments 中的环境,直到找到合格的焦点项。该属性用于首选焦点搜索,并不是定义“按上键必须进入上一个 section”的导航路由表。preferredFocusEnvironments 文档

如果需要在重新进入 collection view 时恢复原来的 item,可以考虑:

collectionView.remembersLastFocusedIndexPath = true

这个设置决定进入 collection view 后优先落在哪个 item。如果 Section 2 根本没有成为目标,它不能单独修复跨分区导航。remembersLastFocusedIndexPath 文档

用调试信息确认焦点实际去了哪里

在包含三个分区的控制器中,可临时记录焦点变化:

override func didUpdateFocus(
    in context: UIFocusUpdateContext,
    with coordinator: UIFocusAnimationCoordinator
) {
    super.didUpdateFocus(in: context, with: coordinator)

    print("heading:", context.focusHeading)
    print("previous:", context.previouslyFocusedView as Any)
    print("next:", context.nextFocusedView as Any)
}

这段日志记录已经发生的焦点变化,不能展示全部被排除的候选项。复现后暂停程序,再在 Swift LLDB 上下文执行:

po UIFocusDebugger.status()
po UIFocusDebugger.checkFocusability(for: targetCell)
po UIFocusDebugger.preferredFocusEnvironments(for: targetCell)

其中 targetCell 必须替换为当前调试作用域中 Section 2 的一个实际 UICollectionViewCell 引用。检查内部 cell 比只检查 collection view 容器更接近真正需要获得焦点的对象。UIFocusDebugger 用于 LLDB 调试,不应作为业务代码中的修复逻辑。UIFocusDebugger 文档

排查期间,先移除焦点回调内的 reloadData()、布局修改,以及反复调用的 setNeedsFocusUpdate()、updateFocusIfNeeded(),观察异常是否仍然存在,再逐项恢复。

何时使用显式焦点更新或 UIFocusGuide?

如果问题发生在数据更新或主动切换内容后,可以等目标完成布局,再由同时包含当前焦点和目标的环境请求焦点更新。不要只对尚未包含当前焦点的目标 cell 调用 setNeedsFocusUpdate();Apple 文档说明,这种调用不会产生效果。setNeedsFocusUpdate 文档

如果已经确认 Section 2 的目标可聚焦,问题出在方向搜索的几何路径,可以评估 UIFocusGuide:把 guide 放在向上移动会经过的区域,并将其 preferredFocusEnvironments 指向 Section 2 的有效目标。Guide 需要实际参与方向搜索,位置和尺寸都必须结合当前布局确定。UIFocusGuide 文档

不要一开始就在每次 3 → 1 后强制跳回 Section 2。这会增加焦点更新次数,也可能掩盖布局或层级问题。

如何判断是否需要针对 tvOS 26 调整?

现有材料没有足够依据证明 tvOS 26 要求这类嵌套列表采用新的处理方式。tvOS 18.5 正常、26.3 异常,是版本相关回归的线索;还需要排除构建工具、SDK 和运行环境同时变化带来的影响。

建议建立一个只包含三个分区和静态数据的最小示例,移除所有自定义焦点逻辑后重试,并记录系统、Xcode、构建 SDK、设备及模拟器版本。若条件允许,使用相同构建产物对比不同运行系统。

如果最小示例仍能稳定复现,应通过 Feedback Assistant 提交示例、操作步骤、录屏和警告日志。Apple 的 tvOS 发布说明页面也建议对未列出的系统问题提交反馈。tvOS Release Notes

备注:内容仅供参考。