如何在SwiftUI的ViewModifier中传递FocusState?
问题:使用FocusState控制输入框移至顶部的动画失效
成功案例:按钮切换Binding实现动画
通过按钮切换布尔值Binding,可实现输入框获取焦点时平滑移动到屏幕顶部的效果,代码如下:
import SwiftUI let movableFieldID = "MyTextField" struct TopMovable: ViewModifier { @Binding var text: String @Binding var active: Bool var namespace: Namespace.ID func body(content: Content) -> some View { if active { VStack { HStack { Spacer() Button("Dismiss") { withAnimation { active = false } } } TextField("Enter text", text: $text) .textFieldStyle(.roundedBorder) .matchedGeometryEffect(id: movableFieldID, in: namespace) Spacer() } .frame(maxWidth: .infinity, maxHeight: .infinity) } else { content } } } extension View { func topMovable(text: Binding<String>, active: Binding<Bool>, namespace: Namespace.ID) -> some View { self.modifier(TopMovable(text: text, active: active, namespace: namespace)) } } struct ContentView: View { @Namespace private var namespace @State var active: Bool = false @State var text: String = "" var body: some View { VStack { Text("Hello, world!") Text("Hi!") TextField("Enter text", text: $text) .matchedGeometryEffect(id: movableFieldID, in: namespace) .textFieldStyle(.roundedBorder) Text("Howdy!") Button("Move the field") { withAnimation { active.toggle() } } } .topMovable(text: $text, active: $active, namespace: namespace) .padding() } }
效果:输入框点击按钮后会平滑移动到顶部,显示Dismiss按钮,点击可返回原位置。
失败尝试:FocusState控制导致无响应/动画失效
改用FocusState直接控制active状态后,输入框完全无响应,即使恢复响应也无法实现动画效果,代码如下:
import SwiftUI let movableFieldID = "MyTextField" struct TopMovable: ViewModifier { @Binding var text: String @FocusState var active: Bool var namespace: Namespace.ID func body(content: Content) -> some View { if active { VStack { HStack { Spacer() Button("Dismiss") { withAnimation { active = false } } } TextField("Enter text", text: $text) .textFieldStyle(.roundedBorder) .matchedGeometryEffect(id: movableFieldID, in: namespace) Spacer() } .frame(maxWidth: .infinity, maxHeight: .infinity) } else { content } } } extension View { func topMovable(text: Binding<String>, active: FocusState<Bool>, namespace: Namespace.ID) -> some View { self.modifier(TopMovable(text: text, active: active, namespace: namespace)) } } struct ContentView: View { @Namespace private var namespace @FocusState var active: Bool @State var text: String = "" var body: some View { VStack { Text("Hello, world!") Text("Hi!") TextField("Enter text", text: $text) .focused($active) .matchedGeometryEffect(id: movableFieldID, in: namespace) .textFieldStyle(.roundedBorder) Text("Howdy!") } .topMovable(text: $text, active: _active, namespace: namespace) .padding() } }
问题表现:点击输入框无任何响应,无法触发移动动画。
解决方案
核心问题是ViewModifier中直接使用FocusState会破坏视图层级的焦点管理,正确的做法是分离焦点状态和动画控制状态,通过Binding同步两者:
修正后的完整代码
import SwiftUI let movableFieldID = "MyTextField" struct TopMovable: ViewModifier { @Binding var text: String @Binding var active: Bool var namespace: Namespace.ID func body(content: Content) -> some View { if active { VStack { HStack { Spacer() Button("Dismiss") { withAnimation { active = false } } } TextField("Enter text", text: $text) .textFieldStyle(.roundedBorder) .matchedGeometryEffect(id: movableFieldID, in: namespace) .focused($active) // 绑定焦点,确保顶部输入框能正确失焦 Spacer() } .frame(maxWidth: .infinity, maxHeight: .infinity) } else { content } } } extension View { func topMovable(text: Binding<String>, active: Binding<Bool>, namespace: Namespace.ID) -> some View { self.modifier(TopMovable(text: text, active: active, namespace: namespace)) } } struct ContentView: View { @Namespace private var namespace @FocusState private var isFocused: Bool // 单独管理焦点状态 @State private var active: Bool = false // 管理动画/视图切换 @State private var text: String = "" var body: some View { VStack { Text("Hello, world!") Text("Hi!") TextField("Enter text", text: $text) .focused($isFocused) .matchedGeometryEffect(id: movableFieldID, in: namespace) .textFieldStyle(.roundedBorder) Text("Howdy!") } .topMovable(text: $text, active: $active, namespace: namespace) .padding() // 同步焦点状态到动画控制状态,触发动画 .onChange(of: isFocused) { newValue in withAnimation { active = newValue } } // 动画状态关闭时,同步失去焦点 .onChange(of: active) { newValue in if !newValue { isFocused = false } } } }
关键修正说明
- 分离状态管理:用
@FocusState var isFocused专门处理输入框焦点,@State var active控制视图切换和动画,避免直接在Modifier中使用FocusState - 双向状态同步:通过
onChange监听两个状态的变化,确保焦点变化触发动画,动画关闭时同步失焦 - 顶部输入框绑定焦点:在Modifier内部的TextField添加
.focused($active),保证点击Dismiss按钮时输入框能正确失去焦点,避免状态不一致
内容的提问来源于stack exchange,提问作者Tres
相关产品推荐
相关产品推荐

