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

SwiftUI:密码输入框浮动占位符点击时未如期触发动画

SwiftUI浮动输入框问题修复与代码审查

问题根源

密码输入框点击时占位符无动画的核心原因:

  • SecureField未实现onEditingChanged回调,导致输入框获取焦点时isActive状态未更新,只有输入内容改变text时才会间接触发状态变化。
  • 代码存在变量名逻辑混淆:CustomTextView的isActive参数实际控制占位符位置,但传入条件(!self.isActive && self.text.isEmpty)易引发逻辑误解。

修复方案

1. 给SecureField添加状态更新回调

在SecureField中添加与TextField一致的onEditingChanged处理,确保点击输入框时立即更新激活状态:

SecureField("", text: $text, onEditingChanged: { editing in
    self.isActive = editing
    print("----FieldActive \(self.isActive)")
}, onCommit: {})

2. 修正逻辑混淆与变量命名

将CustomTextView的isActive参数重命名为isPlaceholderInField,让逻辑更直观:

  • 输入框未激活且文本为空时,占位符显示在输入框内(isPlaceholderInField = true)
  • 输入框激活或文本不为空时,占位符浮动到上方(isPlaceholderInField = false)

3. 修复图标参数错误

左侧图标处的ImageView(name: rightIcon)改为ImageView(name: leftIcon),避免图标显示错误。

4. 用枚举替代字符串类型判断

将type的字符串参数改为枚举,避免硬编码拼写错误:

enum TextFieldType: String {
    case email, password
}

完整修正后的代码

FloatingTextFieldView.swift

import SwiftUI

@available(iOS 15.0, *)
struct FloatingTextFieldView: View {
    var type: TextFieldType
    var placeholder: String
    var rightIcon: String?
    var leftIcon: String?
    
    @Binding var text: String
    var onTap: () -> Void = {}
    
    private enum Field: Int, Hashable {
        case fieldName
    }
    
    @State private var isActive: Bool = false
    @FocusState private var focusField: Field?

    var body: some View {
        VStack(alignment: .leading) {
            ZStack(alignment: .leading) {
                HStack {
                    if let leftIcon = leftIcon {
                        ImageView(name: leftIcon)
                            .onTapGesture {
                                print("----Left Icon Tapped with \(text)")
                            }
                    }
                    if type == .password {
                        SecureField("", text: $text, onEditingChanged: { editing in
                            self.isActive = editing
                            print("----FieldActive \(self.isActive)")
                        }, onCommit: {})
                        .padding(.leading, leftIcon == nil ? 0 : 8)
                        .padding(.trailing, rightIcon == nil ? 0 : 8)
                        .focused($focusField, equals: .fieldName)
                    } else {
                        TextField("", text: $text, onEditingChanged: { editing in
                            self.isActive = editing
                            print("----FieldActive \(self.isActive)")
                        }) {
                        }
                        .padding(.leading, leftIcon == nil ? 0 : 8)
                        .padding(.trailing, rightIcon == nil ? 0 : 8)
                        .focused($focusField, equals: .fieldName)
                    }
                    if let rightIcon = rightIcon {
                        ImageView(name: rightIcon)
                            .onTapGesture {
                                print("----Right Icon Tapped with \(text)")
                            }
                    }
                }
                .padding()
                .overlay(
                    RoundedRectangle(cornerRadius: 5)
                        .stroke(Color.gray, lineWidth: 1)
                )
                
                CustomTextView(
                    placeholder: placeholder,
                    color: .red,
                    backgroundColor: .white,
                    callback: {},
                    isPlaceholderInField: !isActive && text.isEmpty
                )
            }
        }
        .padding()
    }
}

enum TextFieldType: String {
    case email, password
}

CustomTextView.swift

import SwiftUI

struct CustomTextView: View {
    var placeholder: String? = ""
    var color: Color
    var backgroundColor: Color
    var callback: ()->Void
    var isPlaceholderInField: Bool
    
    var body: some View {
        Text(placeholder ?? "placeholder")
            .foregroundColor(color)
            .padding(.horizontal, 10)
            .background(backgroundColor)
            .offset(x: isPlaceholderInField ? 10 : 20, y: isPlaceholderInField ? -35 : 0)
            .scaleEffect(isPlaceholderInField ? 0.8 : 1, anchor: .leading)
            .animation(.easeInOut(duration: 0.4), value: isPlaceholderInField)
            .onTapGesture {
                callback()
            }
    }
}

额外优化点

  • 修正CutomTextView的拼写错误为CustomTextView,消除编译问题。
  • 给左右图标点击的打印信息添加区分,方便调试。
  • 让CustomTextView的点击手势调用传入的callback,增强组件交互性。
  • 使用枚举TextFieldType替代字符串判断,提升代码类型安全性与可读性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 01:27:12