如何在可复用TextField组合视图中检测FocusState变化
实现方案
核心逻辑是把焦点判断封装在自定义组件内部,不需要依赖外部传入的焦点枚举值,利用SwiftUI焦点系统的自动透传、多绑定同步特性实现效果。
底层逻辑说明
SwiftUI的焦点系统会沿视图树自动向下匹配可聚焦控件(比如TextField),外层添加的.focused修饰符不会被中间的容器视图拦截,会自动绑定到内部符合要求的可聚焦子视图上,这也是你把TextField包在自定义容器里,外部焦点设置依然生效的原因。
同时系统支持同一个可聚焦控件的焦点状态同步到多个@FocusState绑定上,外部的焦点枚举绑定、内部的焦点判断绑定不会产生冲突。
具体实现代码
你只需要在自定义的MyCustomTextField内部新增一个私有的布尔类型@FocusState,绑定到内部的TextField上,直接用这个状态控制下划线颜色即可,外部调用代码不需要做任何修改。
struct MyCustomTextField: View { let placeholder: String @Binding var text: String // 内部私有焦点状态,自动监听当前输入框的焦点情况 @FocusState private var isFocused: Bool var body: some View { VStack(spacing: 8) { TextField(placeholder, text: $text) .focused($isFocused) // 绑定内部焦点状态 .padding(.horizontal) // 原有下划线逻辑,直接使用内部焦点状态判断颜色 Rectangle() .fill(isFocused ? Color.blue : Color.gray) .frame(maxWidth: .infinity, minHeight: 2, maxHeight: 2) .padding(.horizontal) } } }
不要把外部的焦点枚举作为参数传入自定义组件做判断,这种方式会让组件和业务层的焦点定义强耦合,失去复用性,上述内部封装的方案完全不需要感知外部的焦点逻辑。
外部调用方式
你之前写的父视图代码完全不需要调整,和使用原生TextField的逻辑完全一致:
struct ContentView: View { private enum FocusedField { case first case second case third } @FocusState private var focusedField: FocusedField? @State private var firstText = "" @State private var secondText = "" @State private var thirdText = "" var body: some View { VStack(spacing: 24) { MyCustomTextField(placeholder: "第一个输入框", text: $firstText) .focused($focusedField, equals: .first) MyCustomTextField(placeholder: "第二个输入框", text: $secondText) .focused($focusedField, equals: .second) MyCustomTextField(placeholder: "第三个输入框", text: $thirdText) .focused($focusedField, equals: .third) } } }
扩展能力
如果需要在输入框获焦/失焦时触发额外逻辑,直接在自定义组件内部监听isFocused的变化即可,不需要外部做任何处理:
// 加在MyCustomTextField的VStack下 .onChange(of: isFocused) { focused in if focused { // 获焦时的自定义逻辑,比如埋点、触发动画 } else { // 失焦时的自定义逻辑,比如输入格式校验 } }
内容的提问来源于stack exchange,提问作者Daniel
相关产品推荐
相关产品推荐

