SwiftUI中如何将@FocusState传递给自定义子View实现TextField复用
@FocusState 跨视图传递及自定义TextField复用方案
适配现有代码的直接传递方案
你可以通过泛型对子视图进行扩展,接收FocusState的投影绑定值和对应匹配项,完全复用原有代码的枚举聚焦逻辑,改后完整代码如下:
子视图View2改造
// 加泛型约束,支持任意Hashable类型的聚焦枚举 struct View2<FocusValue: Hashable>: View { @Binding var text: String let placeholder: String // 新增聚焦绑定属性和当前视图对应的匹配值 var focusedBinding: FocusState<FocusValue?>.Binding let focusValue: FocusValue var body: some View { HStack { TextField(placeholder, text: $text) .frame(minHeight: 44) .padding(.leading, 8) // 直接绑定传入的聚焦状态 .focused(focusedBinding, equals: focusValue) if text.count > 0 { Image(systemName: "xmark.circle.fill") .font(.headline) .foregroundColor(.secondary) .padding(.trailing, 8) // 补充清空按钮点击逻辑 .onTapGesture { text = "" } } } } }
父视图View1调用
struct View1: View { enum Field: Hashable { case username, password } @State var passwordText: String = "" @FocusState var focusedField: Field? var body: some View { // 直接传入聚焦绑定和对应匹配项即可 View2( text: $passwordText, placeholder: "Password", focusedBinding: $focusedField, focusValue: .password ) // 原有控制逻辑完全可用,比如点击按钮自动聚焦密码框 Button("登录") { if passwordText.isEmpty { focusedField = .password } } } }
如果不需要多枚举匹配,只需要单个Bool控制聚焦,也可以简化子视图,直接接收Binding<Bool>参数,父视图自行做状态转换即可:
// 简化版View2 struct View2: View { @Binding var text: String let placeholder: String @Binding var isFocused: Bool var body: some View { HStack { TextField(placeholder, text: $text) .frame(minHeight: 44) .padding(.leading, 8) .focused($isFocused) // 省略其他逻辑 } } } // 父视图调用时的状态转换 View2( text: $passwordText, placeholder: "Password", isFocused: .init( get: { focusedField == .password }, set: { focusedField = $0 ? .password : nil } ) )
更优的通用自定义TextField复用方案
可以把自定义TextField封装为通用组件,支持可选的聚焦绑定、密码模式、清空按钮开关、键盘类型配置、回车回调等常用功能,全项目可直接复用,示例代码如下:
struct CustomTextField<FocusValue: Hashable>: View { @Binding var text: String let placeholder: String // 聚焦参数设为可选,不需要控制聚焦时可以不传 var focusedBinding: FocusState<FocusValue?>.Binding? = nil let focusValue: FocusValue? var isSecure: Bool = false var showClearButton: Bool = true var keyboardType: UIKeyboardType = .default var returnKeyType: UIReturnKeyType = .default var onSubmit: (() -> Void)? = nil var body: some View { HStack(spacing: 8) { Group { if isSecure { SecureField(placeholder, text: $text) } else { TextField(placeholder, text: $text) } } .frame(minHeight: 44) .keyboardType(keyboardType) .submitLabel(returnKeyType.toSubmitLabel) .onSubmit { onSubmit?() } // 可选绑定聚焦状态 .ifLet(focusedBinding, value: focusValue) { view, binding, value in view.focused(binding, equals: value) } if showClearButton && !text.isEmpty { Button { text = "" } label: { Image(systemName: "xmark.circle.fill") .foregroundColor(.secondary) } } } .padding(.horizontal, 8) } } // 辅助扩展:UIReturnKeyType转SwiftUI的SubmitLabel extension UIReturnKeyType { var toSubmitLabel: SubmitLabel { switch self { case .done: return .done case .go: return .go case .send: return .send case .join: return .join case .route: return .route case .search: return .search case .next: return .next case .continue: return .continue default: return .return } } } // 辅助扩展:支持可选值的视图链式调用 extension View { @ViewBuilder func ifLet<T1, T2, V: View>(_ value1: T1?, value2: T2, @ViewBuilder transform: (Self, T1, T2) -> V) -> some View { if let value1 = value1 { transform(self, value1, value2) } else { self } } }
通用组件使用示例
// 需要控制聚焦的场景 CustomTextField( text: $passwordText, placeholder: "请输入密码", focusedBinding: $focusedField, focusValue: Field.password, isSecure: true, keyboardType: .asciiCapable, returnKeyType: .done ) { // 点击回车的回调逻辑 login() } // 不需要控制聚焦的场景 CustomTextField( text: $nicknameText, placeholder: "请输入昵称", focusValue: nil )
内容的提问来源于stack exchange,提问作者Luis Ramirez
相关产品推荐
相关产品推荐

