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

SwiftUI中子视图焦点管理:实现键盘工具栏焦点切换

解决方案:跨子视图的TextField焦点切换

要实现全局键盘工具栏统一控制所有子视图内TextField的焦点切换,核心思路是将子视图的焦点状态提升到父视图管理,通过绑定让父视图能直接控制子视图内部的TextField焦点,避免每个子视图单独显示工具栏。

步骤1:重构ChildView,暴露焦点绑定

把ChildView内部的@FocusState改为@Binding,让父视图可以直接控制其内部TextField的焦点:

struct ChildView: View {
    var index: Int
    @State var field1: String = ""
    @State var field2: String = ""
    @State var field3: String = ""
    // 用绑定替代内部FocusState,交给父视图管理
    @Binding var focusedField: Int?
    
    var body: some View {
        VStack {
            Text(String(index))
            TextField("Field 1", text: $field1)
                .focused($focusedField, equals: 1)
            TextField("Field 2", text: $field2)
                .focused($focusedField, equals: 2)
            TextField("Field 3", text: $field3)
                .focused($focusedField, equals: 3)
        }
    }
}

步骤2:父视图统一管理所有焦点状态

在ParentView中维护每个子视图的焦点状态,并实现焦点切换逻辑:

struct ParentView: View {
    // 跟踪当前有焦点的子视图索引
    @FocusState var activeChildIndex: Int?
    // 存储每个子视图的内部焦点字段(key=子视图索引,value=字段编号1/2/3)
    @State private var childFieldFocus: [Int: Int?] = [:]
    
    // 子视图总数和每个子视图的字段数
    private let childCount = 4
    private let fieldsPerChild = 3
    
    var body: some View {
        ScrollView {
            ForEach(1...childCount, id: \.self) { index in
                ChildView(index: index, focusedField: $childFieldFocus[index, default: nil])
                    // 监听子视图焦点变化,更新活跃子视图索引
                    .onChange(of: childFieldFocus[index]) { newValue in
                        if newValue != nil {
                            activeChildIndex = index
                        } else if activeChildIndex == index {
                            activeChildIndex = nil
                        }
                    }
            }
        }
        .toolbar {
            ToolbarItemGroup(placement: .keyboard) {
                Button {
                    focusPrevious()
                } label: {
                    Image(systemName: "chevron.left")
                }
                .disabled(activeChildIndex == nil)

                Button {
                    focusNext()
                } label: {
                    Image(systemName: "chevron.right")
                }
                .disabled(activeChildIndex == nil)
                
                Spacer()
                
                Button {
                    // 清空所有焦点,收起键盘
                    childFieldFocus.values.forEach { $0 = nil }
                    activeChildIndex = nil
                } label: {
                    Image(systemName: "keyboard.chevron.compact.down")
                }
            }
        }
    }
    
    // 切换到上一个焦点字段
    private func focusPrevious() {
        guard let currentChild = activeChildIndex else { return }
        let currentField = childFieldFocus[currentChild] ?? 1
        
        if currentField > 1 {
            // 当前子视图内切换到上一个字段
            childFieldFocus[currentChild] = currentField - 1
        } else {
            // 切换到上一个子视图的最后一个字段,循环到最后一个子视图如果当前是第一个
            let prevChild = currentChild > 1 ? currentChild - 1 : childCount
            childFieldFocus[currentChild] = nil
            childFieldFocus[prevChild] = fieldsPerChild
            activeChildIndex = prevChild
        }
    }
    
    // 切换到下一个焦点字段
    private func focusNext() {
        guard let currentChild = activeChildIndex else { return }
        let currentField = childFieldFocus[currentChild] ?? 1
        
        if currentField < fieldsPerChild {
            // 当前子视图内切换到下一个字段
            childFieldFocus[currentChild] = currentField + 1
        } else {
            // 切换到下一个子视图的第一个字段,循环到第一个子视图如果当前是最后一个
            let nextChild = currentChild < childCount ? currentChild + 1 : 1
            childFieldFocus[currentChild] = nil
            childFieldFocus[nextChild] = 1
            activeChildIndex = nextChild
        }
    }
}

核心逻辑说明

  1. 焦点状态提升:通过绑定将子视图的焦点状态交给父视图管理,父视图可以直接读写每个子视图的当前焦点字段。
  2. 活跃子视图跟踪:通过onChange监听子视图的焦点变化,更新activeChildIndex,确保工具栏按钮知道当前要操作哪个子视图。
  3. 切换逻辑:
    • 同一子视图内:直接增减字段编号即可切换焦点。
    • 跨子视图切换:当前子视图的最后一个字段切换到下一个子视图的第一个字段,第一个字段切换到上一个子视图的最后一个字段,实现循环切换。
  4. 工具栏统一显示:工具栏添加在父视图的ScrollView上,只会显示一组按钮,不会随子视图重复出现。

内容的提问来源于stack exchange,提问作者L. Mees

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 22:30:30