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

SwiftUI中如何实现嵌套的FocusState?

嵌套焦点状态的问题分析与解决方案

SwiftUI的@FocusState本身不支持同时存在多个独立的焦点层级——当内层可聚焦视图(比如TextField)获取焦点时,系统会自动释放外层视图的焦点,这是因为焦点系统在同一时刻只会让一个“焦点域”内的一个视图持有焦点。你的核心问题在于用焦点状态管理视图的展开/折叠逻辑,这本身就是思路偏差:展开状态属于视图的展示状态,而非焦点状态,不该绑定到焦点系统上。

正确的解决方案:分离展开状态与焦点状态

1. 用独立状态管理列表项的展开

放弃用@FocusState跟踪展开,改用@State或@Binding的集合/字典来记录哪些项处于展开状态,比如:

@State private var expandedItems: Set<UUID> = []

点击列表项时,直接切换对应ID在集合中的存在状态,控制展开/折叠:

Text(item.title)
    .onTapGesture {
        expandedItems.toggle(item.id)
    }

2. 独立管理内层TextField的焦点

为内层的TextField单独设置焦点状态,可以选择两种方式:

  • 单焦点跟踪:用一个@FocusState变量跟踪当前聚焦的TextField ID,适合批量控制:
    @FocusState private var focusedTextFieldId: UUID?
    
    // 在TextField上绑定
    TextField("Detail", text: $item.detail)
        .focused($focusedTextFieldId, equals: item.id)
    
  • 子视图独立焦点:把展开后的内容封装成子视图,在子视图内部定义专属的@FocusState变量,每个子视图的焦点状态完全独立:
    struct ItemDetailView: View {
        @Binding var item: Item
        @FocusState private var isDetail1Focused: Bool
        @FocusState private var isDetail2Focused: Bool
    
        var body: some View {
            TextField("Detail 1", text: $item.detail1)
                .focused($isDetail1Focused)
            TextField("Detail 2", text: $item.detail2)
                .focused($isDetail2Focused)
    
            Button("聚焦第一个输入框") {
                isDetail1Focused = true
            }
        }
    }
    

完整代码示例

struct Item: Identifiable {
    let id = UUID()
    var title: String
    var detail1: String
    var detail2: String
}

struct ContentView: View {
    @State private var items: [Item] = [
        Item(title: "项目1", detail1: "详情1", detail2: "详情2"),
        Item(title: "项目2", detail1: "详情3", detail2: "详情4")
    ]
    @State private var expandedItems: Set<UUID> = []
    @FocusState private var focusedTextFieldId: UUID?

    var body: some View {
        List {
            ForEach($items) { $item in
                VStack(alignment: .leading) {
                    Text(item.title)
                        .font(.headline)
                        .padding(.vertical, 8)
                        .onTapGesture {
                            expandedItems.toggle(item.id)
                        }

                    if expandedItems.contains(item.id) {
                        TextField("详情1", text: $item.detail1)
                            .focused($focusedTextFieldId, equals: item.id)
                            .textFieldStyle(.roundedBorder)
                        TextField("详情2", text: $item.detail2)
                            .focused($focusedTextFieldId, equals: item.id)
                            .textFieldStyle(.roundedBorder)

                        Button("聚焦第一个输入框") {
                            focusedTextFieldId = item.id
                        }
                        .padding(.top, 4)
                    }
                }
            }
        }
    }
}

// 给Set扩展toggle方法,简化操作
extension Set where Element: Hashable {
    mutating func toggle(_ element: Element) {
        if contains(element) {
            remove(element)
        } else {
            insert(element)
        }
    }
}

这种方式下,展开状态由专门的状态变量管理,和TextField的焦点状态完全分离,不会出现TextField获焦导致列表项折叠的问题。

内容的提问来源于stack exchange,提问作者Joseph Humfrey

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 06:13:24