You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI tvOS向下导航时如何让底部HStack首个按钮A获取焦点

tvOS SwiftUI 自定义向下焦点跳转实现方案

核心原理

系统默认焦点导航按几何距离最近原则选中目标元素,要实现固定跳转到底部第一个按钮A,通过tvOS 15+原生提供的@FocusState和onMoveFocusAPI手动接管焦点跳转逻辑即可,无需引入第三方依赖或桥接复杂UIKit逻辑,和你现有代码的版本适配要求完全匹配。

实现步骤

  • 定义遵守Hashable协议的焦点枚举,为所有可交互按钮分配唯一标识
  • 添加@FocusState属性,实时追踪当前持有的焦点元素
  • 给所有按钮绑定对应的焦点ID,建立状态和UI元素的关联
  • 给顶部按钮所在的焦点区域添加onMoveFocus拦截逻辑,检测到用户触发向下导航时,直接将焦点赋值给底部按钮A,返回true告知系统本次跳转已由自定义逻辑处理,不再执行默认的最近元素匹配规则。

完整修改后代码

import SwiftUI

// 标记所有可聚焦元素的唯一身份
enum FocusTarget: Hashable {
    case topBtn1, topBtn2, topBtn3
    case bottomBtnA, bottomBtnB, bottomBtnC
}

struct DummyView: View {
    @Environment(\.presentationMode) var presentationMode
    // 焦点状态绑定
    @FocusState private var currentFocus: FocusTarget?

    var body: some View {
        ZStack {
            // 原代码未显式声明容器,两个全尺寸View实际为叠放关系,用ZStack明确布局逻辑
            contentView
            parent
        }
    }

    private var parent: some View {
        VStack {
            if #available(tvOS 15.0, *) {
                HStack {
                    Spacer()
                    Button ("1") {}
                        .focused($currentFocus, equals: .topBtn1)
                    Button ("2") {}
                        .focused($currentFocus, equals: .topBtn2)
                    Button ("3") {}
                        .focused($currentFocus, equals: .topBtn3)
                    Spacer()
                }
                .focusSection()
                .border(Color.white, width: 2)
                // 拦截顶部区域的焦点移动事件
                .onMoveFocus { direction in
                    guard direction == .down else { return false }
                    // 向下操作时强制将焦点移至底部按钮A
                    currentFocus = .bottomBtnA
                    return true
                }
            } else {
                // 低版本适配逻辑可在此补充
            }

            Spacer()
            if #available(tvOS 15.0, *) {
                HStack {
                    Button ("A") {}
                        .focused($currentFocus, equals: .bottomBtnA)
                    Spacer()
                    Button ("B") {}
                        .focused($currentFocus, equals: .bottomBtnB)
                    Spacer()
                    Button ("C") {}
                        .focused($currentFocus, equals: .bottomBtnC)
                }
                .border(Color.white, width: 2)
                .focusSection()
            } else {
                // 低版本适配逻辑可在此补充
            }
        }
    }

    private var contentView: some View {
        VStack {
            Spacer()
            Text("THIS IS DUMMY SCREEN")
            Spacer()
        }
    }
}

补充说明

  • 上述逻辑仅拦截顶部区域的向下导航操作,其余交互(顶部按钮左右切换、底部按钮左右/向上导航)均走系统默认逻辑,不会破坏原有操作体验
  • 如果需要适配tvOS 15以下版本,可通过UIViewRepresentable桥接UIKit的UIFocusEnvironment协议,重写preferredFocusEnvironments实现自定义跳转
  • 若需要指定页面初始焦点,可在页面.onAppear回调中给currentFocus赋值对应枚举值即可

运行效果示意图

内容的提问来源于stack exchange,提问作者Abhishek Thapliyal

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.03 04:48:30