SwiftUI中使用List+@FocusState监听焦点时如何避免滚动频繁重建?
解决SwiftUI List滚动时因@FocusState监听导致的频繁重建问题
问题分析
你遇到的核心问题是:使用List配合@FocusState跟踪焦点,并通过.onChange(of: focus)实现聚焦输入框的滚动定位时,List滚动会触发ContentView频繁重建,复杂内容下卡顿明显。排查可知,即使.onChange闭包未打印内容(仅手动聚焦时触发),滚动过程中_focus仍会被标记为状态变化,进而引发视图重建。
解决方案
核心思路是只监听焦点的实际变化事件,规避SwiftUI内部状态波动导致的不必要重建。可以用@FocusState的publisher结合.onReceive替代.onChange,具体实现如下:
修改后的代码
struct ContentView: View { enum Field: Hashable { case fieldId(Int) } @FocusState var focus: Field? @State var text: String = "" var body: some View { List { let _ = Self._printChanges() ForEach(0..<100) { index in TextField("输入第\(index)项内容", text: $text) .id(Field.fieldId(index)) .focused($focus, equals: .fieldId(index)) } } // 替换原.onChange,通过publisher监听焦点实际变化 .onReceive(focus.publisher) { newFocus in guard newFocus != nil else { return } print("仅在焦点实际变化时触发") // 此处添加ScrollViewReader滚动逻辑,示例: // withAnimation { // scrollProxy.scrollTo(newFocus, anchor: .center) // } } } }
原理说明
focus.publisher仅在focus的值真正发生改变时发送事件,不会响应List滚动时SwiftUI内部的临时焦点调整操作。- 相比
.onChange(of: focus),这种方式不会因无关的状态标记触发视图重建,同时完整保留了监听焦点变化的能力,且不影响List的分组、编辑(删除/移动项)等原有功能。
额外优化建议
若列表项为复杂视图,可将TextField封装为独立子视图,进一步隔离状态变化,缩小重建范围:
struct FocusTextField: View { let index: Int @Binding var text: String @FocusState.Binding var isFocused: Bool var body: some View { TextField("输入第\(index)项内容", text: $text) .focused($isFocused) } } // 在ContentView中调用: ForEach(0..<100) { index in FocusTextField( index: index, text: $text, isFocused: $focus, equals: .fieldId(index) ) .id(Field.fieldId(index)) }
内容的提问来源于stack exchange,提问作者Sergey
相关产品推荐
相关产品推荐

