mcpField
F
focus-engine

focus-engine

github.com/dpearson2699开发1.2k2.7k 次安装13 周前更新
代码仓库

关于 focus-engine

focus-engine 是一项开发技能,指导在 SwiftUI 和 UIKit 应用中实现键盘与方向焦点。内容涵盖焦点状态、场景值、焦点恢复和平台相关路由,并区分辅助功能焦点与 visionOS 凝视输入。

主要功能

  1. 01使用 SwiftUI FocusState 管理焦点
  2. 02设置默认焦点目标
  3. 03提供焦点值和场景值
  4. 04用焦点分区引导方向移动
  5. 05恢复焦点并用 UIKit 焦点指南路由

适用场景

  1. 01控制表单字段间的键盘焦点
  2. 02导航 macOS 或 tvOS 的不规则布局
  3. 03让命令关联当前场景焦点内容
  4. 04关闭覆盖层后恢复焦点

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/dpearson2699/swift-ios-skills --skill focus-engine

安装后在 Claude Code 中输入/focus-engine确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

evals/
evals.json
references/
focus-debugging.md
focus-patterns.md
multi-platform-focus.md

详细介绍

焦点引擎

适用于面向 iOS 26+、iPadOS、macOS、tvOS 和 visionOS 连接输入路径的 SwiftUI 与 UIKit 应用的焦点行为。涵盖键盘焦点、方向焦点、场景焦点值、焦点恢复和 UIKit 焦点引导。此技能中关于 focusSection() 的指南适用于 macOS 和 tvOS。visionOS 中由注视驱动的悬停是一种输入方式,而非焦点。VoiceOver 和 Switch Control 的无障碍专用焦点内容请参阅 ios-accessibility 技能。

当请求同时涉及焦点、无障碍或空间输入时,请明确界定范围:

  • 键盘、遥控器、游戏控制器和场景焦点行为请使用此技能。
  • 对于 visionOS,请将注视、直接触摸和指针定位描述为悬停/输入方式,而非焦点。
  • 对于 VoiceOver、Switch Control、Voice Control 或无障碍元素排序,请简要转交给 ios-accessibility。

目录

SwiftUI FocusState

使用 @FocusState 在场景中读取和设置焦点位置。单个目标使用 Bool,多个目标使用可选的 Hashable 枚举。

struct LoginView: View {
    enum Field: Hashable { case email, password }

    @State private var email = ""
    @State private var password = ""
    @FocusState private var focusedField: Field?

    var body: some View {
        Form {
            TextField("Email", text: $email)
                .focused($focusedField, equals: .email)

            SecureField("Password", text: $password)
                .focused($focusedField, equals: .password)
        }
        .onAppear { focusedField = .email }
        .onSubmit {
            switch focusedField {
            case .email: focusedField = .password
            case .password, nil: submit()
            }
        }
    }
}

将焦点状态保留在拥有可聚焦控件的视图中。

默认焦点

使用 .defaultFocus 设置视图出现时或自动重新分配焦点时首选的初始焦点区域或控件。

struct SidebarView: View {
    enum Target: Hashable { case library, settings }
    @FocusState private var focusedTarget: Target?

    var body: some View {
        VStack {
            Button("Library") { }
                .focused($focusedTarget, equals: .library)

            Button("Settings") { }
                .focused($focusedTarget, equals: .settings)
        }
        .defaultFocus($focusedTarget, .library)
    }
}

每个屏幕或焦点区域最好只设置一个明确的默认目标。

焦点值和场景值

使用焦点值公开当前获得焦点的视图中的状态。如果命令或场景级 UI 在焦点于场景内移动后仍需访问该值,请使用场景焦点值。

struct SelectedRecipeKey: FocusedValueKey {
    typealias Value = Binding<Recipe>
}

extension FocusedValues {
    var selectedRecipe: Binding<Recipe>? {
        get { self[SelectedRecipeKey.self] }
        set { self[SelectedRecipeKey.self] = newValue }
    }
}

struct RecipeDetailView: View {
    @Binding var recipe: Recipe

    var body: some View {
        Text(recipe.title)
            .focusedSceneValue(\.selectedRecipe, $recipe)
    }
}

对于需要操作当前焦点所在场景内容的菜单、命令和工具栏,请使用此模式。

可聚焦交互

对于需要参与键盘或方向焦点的自定义 SwiftUI 视图,请使用 .focusable(_:interactions:)。

struct SelectableCard: View {
    let title: String
    let action: () -> Void
    @FocusState private var isFocused: Bool

    var body: some View {
        Button(action: action) {
            RoundedRectangle(cornerRadius: 12)
                .fill(isFocused ? Color.accentColor.opacity(0.15) : .clear)
                .overlay { Text(title) }
        }
        .buttonStyle(.plain)
        .focusable(interactions: .activate)
        .focused($isFocused)
    }
}

优先使用语义化的 Button、Toggle、TextField 和其他系统控件,而不是让任意手势驱动的视图可聚焦。只有当语义化控件无法表达 UI 时,才对类似按钮的自定义控件使用 .focusable(interactions: .activate)。仅当视图确实需要编辑或多种焦点驱动行为时,才使用范围更广的交互。

焦点区域

在 macOS 13+ 和 tvOS 15+ 上,当不规则布局中默认的左右或上下移动会跳过预期分组时,使用 focusSection() 引导焦点在可聚焦后代视图分组之间移动。

struct TVLibraryView: View {
    var body: some View {
        HStack {
            VStack {
                Button("Recent") { }
                Button("Favorites") { }
                Button("Downloaded") { }
            }
            .focusSection()

            VStack {
                Button("Featured") { }
                Button("Top Picks") { }
                Button("Continue Watching") { }
            }
            .focusSection()
        }
    }
}

当默认的左右或上下移动会跳过预期分组时,请在 macOS 和 tvOS 上使用焦点区域。

焦点恢复

关闭工作表、弹出框或临时覆盖层后,将焦点移回稳定的触发控件或逻辑上的下一个目标。

struct FiltersView: View {
    @State private var showSheet = false
    @FocusState private var isFilterButtonFocused: Bool

    var body: some View {
        Button("Filters") { showSheet = true }
            .focused($isFilterButtonFocused)
            .sheet(isPresented: $showSheet) {
                FilterEditor()
                    .onDisappear {
                        Task { @MainActor in
                            isFilterButtonFocused = true
                        }
                    }
            }
    }
}

每当界面呈现方式的变化可能令用户迷失方向时,都应有意恢复焦点。

UIKit 焦点引导

当 UIKit 或 tvOS 布局需要跨越空白区域或不规则几何区域自定义焦点路径时,请使用 UIFocusGuide。

final class DashboardViewController: UIViewController {
    private let focusGuide = UIFocusGuide()
    @IBOutlet private weak var leadingButton: UIButton!
    @IBOutlet private weak var trailingButton: UIButton!

    override func viewDidLoad() {
        super.viewDidLoad()

        view.addLayoutGuide(focusGuide)
        focusGuide.preferredFocusEnvironments = [trailingButton]

        NSLayoutConstraint.activate([
            focusGuide.leadingAnchor.constraint(equalTo: leadingButton.trailingAnchor),
            focusGuide.trailingAnchor.constraint(equalTo: trailingButton.leadingAnchor),
            focusGuide.topAnchor.constraint(equalTo: leadingButton.topAnchor),
            focusGuide.bottomAnchor.constraint(equalTo: leadingButton.bottomAnchor)
        ])
    }
}

UIFocusGuide 不可见,也不是视图。使用它重定向焦点,无需添加装饰性 UI。

常见错误

  1. 在同一套思路中混淆辅助功能焦点与键盘或方向焦点。
  2. 将 @FocusState 存储在共享模型中,而不是所属视图中。
  3. 在同一屏幕上设置多个互相冲突的默认焦点目标。
  4. 对装饰性视图使用 .focusable()。
  5. 忘记在工作表、弹出窗口或自定义覆盖层关闭后恢复焦点。
  6. 在 macOS 或 tvOS 上未先尝试使用 focusSection(),或在 SwiftUI 中更好地组织布局分组,就直接使用 UIFocusGuide。
  7. 对自定义可聚焦控件使用手势处理器来实现主要操作,而不是尽可能使用语义明确的 Button。
  8. 将 visionOS 的视线悬停视为焦点;仅对键盘和游戏控制器等连接的输入设备使用焦点引导。

审查清单

  • @FocusState 位于拥有这些控件的视图中
  • 如果屏幕需要初始焦点目标,则明确指定
  • 字段或分组之间的焦点移动是确定的
  • 命令需要当前场景状态时,使用 focusedSceneValue 或相关的 focused-value API
  • 仅当自定义控件确实可交互时,才启用焦点
  • 在 macOS 或 tvOS 上,遇到不规则方向布局时,先使用 focusSection(),再考虑转而使用 UIKit
  • 临时界面关闭后,焦点会返回到稳定的元素
  • UIFocusGuide 的几何布局和首选目标与预期路径一致
  • visionOS 引导会区分连接设备焦点与视线驱动的悬停或 RealityKit 输入目标
  • 辅助功能焦点问题在 ios-accessibility 中处理,不与键盘方向焦点逻辑混为一谈

参考资料

评论

支持 Markdown