You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.24 20:15:07