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
相关产品推荐
相关产品推荐

