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

SwiftUI实现密码显隐切换时保持TextField第一响应状态

SwiftUI 密码框显隐切换时保持焦点与键盘不消失

问题场景

我在登录视图中实现了用户名和密码两个输入框,想要通过点击按钮切换密码框的明文/密文显示状态。当前实现代码如下:

ZStack(alignment: .trailing) {
    Group {
        if viewModel.isPasswordVisible {
            SecureField("", text: $viewModel.text)
                .keyboardType(.default)
                .focused($focusField, equals: .secure)
        }
        else {
            TextField("", text: $viewModel.text)
                .keyboardType(.emailAddress)
                .focused($focusField, equals: .plain)
        }
    }
    .autocorrectionDisabled()
    .textInputAutocapitalization(.none)
    .font(.system(size: Constants.Font.regularSize))
    .padding()
    .padding(.trailing, textFieldPaddingTrailing + numberOfSymbolsInTextField)
    
    HStack {
        if viewModel.isSecureField {
            Button(action: {
                viewModel.isPasswordVisible.toggle()
            }) {
                Text(Image(systemName: viewModel.isPasswordVisible ? Constants.Image.eye : Constants.Image.eyeSlash))
                    .font(.system(size: Constants.Font.symbolSize))
                    .foregroundColor(DMSColor.SwiftUIColor.primaryText)
            }
        }
    }
    .padding(.trailing)
}

显隐切换的逻辑正常,但存在一个问题:切换时键盘会自动隐藏。原因是isPasswordVisible切换时,原来的输入框被销毁,新的输入框重建后没有自动获取焦点,导致键盘收起。

解决方案

核心思路是避免销毁重建输入框,通过修改现有输入框的属性来实现显隐切换,同时确保焦点状态被正确保留。

优化后的代码

ZStack(alignment: .trailing) {
    // 使用单个TextField,通过修饰符控制密码显隐
    TextField("", text: $viewModel.text)
        .keyboardType(.default)
        // 统一绑定到同一个焦点标识
        .focused($focusField, equals: .password)
        .autocorrectionDisabled()
        .textInputAutocapitalization(.none)
        .font(.system(size: Constants.Font.regularSize))
        .padding()
        .padding(.trailing, textFieldPaddingTrailing + numberOfSymbolsInTextField)
        // 控制密码是否隐藏:isPasswordVisible为true时显示明文,所以传入!isPasswordVisible
        .secure(!viewModel.isPasswordVisible)
        // 可选:根据显隐状态调整文本内容类型,适配系统行为
        .textContentType(viewModel.isPasswordVisible ? .username : .password)
    
    if viewModel.isSecureField {
        Button(action: {
            withAnimation {
                viewModel.isPasswordVisible.toggle()
            }
            // 切换后强制重新设置焦点,确保输入框保持第一响应
            DispatchQueue.main.async {
                focusField = .password
            }
        }) {
            Text(Image(systemName: viewModel.isPasswordVisible ? Constants.Image.eye : Constants.Image.eyeSlash))
                .font(.system(size: Constants.Font.symbolSize))
                .foregroundColor(DMSColor.SwiftUIColor.primaryText)
        }
        .padding(.trailing)
    }
}

关键修改说明

  1. 替换输入框切换逻辑:不再用if-else创建两个不同的TextField/SecureField,而是使用单个TextField配合secure(_:)修饰符控制密码显隐,这样视图不会被销毁重建,只是修改属性。
  2. 统一焦点绑定:将焦点绑定到同一个标识(比如.password),避免切换时焦点丢失。
  3. 强制恢复焦点:在按钮点击事件中,通过DispatchQueue.main.async延迟设置焦点,确保视图更新后输入框能重新获得第一响应状态,保持键盘不收起。

内容的提问来源于stack exchange,提问作者Shawn Frank

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 04:27:49