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

带SecureTextEntry切换功能的UITextField如何设置安全区域避免文本遮挡

问题原因分析
  • 文本遮挡按钮:你没有手动调整UITextField的文本可编辑区域边距,且直接手动计算按钮的x坐标,导致按钮位置超出了系统预留的rightView区域,文本编辑区域没有避开按钮位置。
  • 图标位置偏右/出现重复图标:调用enablePasswordToggle时UITextField自身的frame可能尚未完成布局,拿到的宽度不准确,手动设置frame的方式无法响应后续textField的尺寸变化;另外如果多次调用enablePasswordToggle也会重复添加按钮导致出现两个图标。

你遇到的异常效果如下:
异常效果截图

修复后的实现代码
import UIKit

private var kToggleButtonKey: UInt8 = 0

extension UITextField {
    // 关联对象存储切换按钮,避免重复添加
    private var passwordToggleButton: UIButton? {
        get {
            objc_getAssociatedObject(self, &kToggleButtonKey) as? UIButton
        }
        set {
            objc_setAssociatedObject(self, &kToggleButtonKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }
    
    fileprivate func setPasswordToggleImage(_ button: UIButton) {
        let imageName = isSecureTextEntry ? "eye-active" : "eye-inactive"
        button.setImage(UIImage(named: imageName), for: .normal)
        // 给按钮加内边距,避免图标贴边
        button.imageEdgeInsets = UIEdgeInsets(top: 0, left: -8, bottom: 0, right: 8)
    }

    func enablePasswordToggle(){
        // 避免重复添加按钮
        guard passwordToggleButton == nil else { return }
        
        let button = UIButton(type: .custom)
        setPasswordToggleImage(button)
        // 只设置尺寸,位置由系统自动管理
        button.frame = CGRect(x: 0, y: 0, width: 33, height: 25)
        button.addTarget(self, action: #selector(togglePasswordView), for: .touchUpInside)
        
        self.rightView = button
        self.rightViewMode = .always
        self.passwordToggleButton = button
    }
    
    @IBAction func togglePasswordView(_ sender: UIButton) {
        self.isSecureTextEntry.toggle()
        setPasswordToggleImage(sender)
        // 修复切换secureTextEntry时的布局异常
        if let existingText = text, isFirstResponder {
            deleteBackward()
            insertText(existingText)
        }
    }
    
    // 调整文本显示区域,避开右侧按钮
    open override func textRect(forBounds bounds: CGRect) -> CGRect {
        var rect = super.textRect(forBounds: bounds)
        if rightViewMode != .never, let _ = rightView {
            rect.size.width -= 33
        }
        return rect
    }
    
    // 调整编辑状态下的文本区域,避开右侧按钮
    open override func editingRect(forBounds bounds: CGRect) -> CGRect {
        var rect = super.editingRect(forBounds: bounds)
        if rightViewMode != .never, let _ = rightView {
            rect.size.width -= 33
        }
        return rect
    }
    
    // 调整rightView的位置,避免偏右
    open override func rightViewRect(forBounds bounds: CGRect) -> CGRect {
        var rect = super.rightViewRect(forBounds: bounds)
        rect.origin.x -= 8
        return rect
    }
}
修复说明
  • 用关联对象存储密码切换按钮,调用enablePasswordToggle时先判断是否已经存在按钮,避免重复添加导致多图标问题
  • 不再手动计算按钮的x坐标,由系统自动排布rightView的位置,重写rightViewRect(forBounds:)微调按钮的水平位置,符合视觉预期
  • 重写textRect和editingRect方法,给文本显示/编辑区域的右侧留出33pt的边距,完全避开按钮区域,不会出现文本遮挡问题
  • 切换isSecureTextEntry后主动刷新文本输入状态,解决系统原生secureEntry切换时的布局异常、文本错位问题
  • 给按钮添加imageEdgeInsets,避免图标和textField边缘贴得过近,视觉效果更协调

注意:如果你的项目中其他UITextField不需要统一修改边距,可以把重写textRect、editingRect、rightViewRect的逻辑放到自定义的密码输入框子类中,避免影响全局UITextField的布局。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.25 07:27:00