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

UIButton自定义类与扩展结合使用出现问题的解决方法

问题分析与解决方案

我之前也碰到过类似的问题,结合你的代码来看,主要是富文本标题未被清空和布局时机依赖错误这两个原因导致的,下面一步步给你解决:

1. 核心问题:标题未被移除的原因

你在typeMain()里调用了self.addCharacterSpacing(),但这个方法是给UILabel设置富文本(attributedText)的。正确的写法应该是给按钮的titleLabel调用:self.titleLabel?.addCharacterSpacing()。

当按钮设置了attributedTitle后,showLoading()里的setTitle("", for: .normal)只会清空普通的title,而富文本标题依然存在,所以加载指示器会叠在标题上面。

修复方案:同时处理普通标题和富文本标题

修改LoadingButton类,保存并恢复富文本标题:

class LoadingButton: UIButton {
    var originalAttributedTitle: NSAttributedString? // 新增:保存富文本标题
    var originalButtonText: String?
    var activityIndicator: UIActivityIndicatorView!

    func showLoading() {
        // 保存原始的富文本和普通文本
        originalAttributedTitle = self.attributedTitle(for: .normal)
        originalButtonText = self.titleLabel?.text
        
        // 同时清空两种标题
        self.setTitle("", for: .normal)
        self.setAttributedTitle(nil, for: .normal)
        
        if activityIndicator == nil {
            activityIndicator = createActivityIndicator()
        }
        showSpinning()
    }

    func hideLoading() {
        // 优先恢复富文本标题,保持字符间距样式
        if let attributedTitle = originalAttributedTitle {
            self.setAttributedTitle(attributedTitle, for: .normal)
        } else {
            self.setTitle(originalButtonText, for: .normal)
        }
        activityIndicator.stopAnimating()
    }

    // ... 其他方法不变,建议把约束写法优化一下:
    private func centerActivityIndicatorInButton() {
        // 用更简洁的约束激活方式
        NSLayoutConstraint.activate([
            activityIndicator.centerXAnchor.constraint(equalTo: self.centerXAnchor),
            activityIndicator.centerYAnchor.constraint(equalTo: self.centerYAnchor)
        ])
    }
}

2. 布局时机问题:frame依赖导致样式异常

在viewDidLoad()调用typeMain()时,AutoLayout还没有完成布局,按钮的frame是不准确的。你用self.frame.size来设置圆角和阴影路径,会导致样式错位,甚至可能影响按钮的点击区域或子视图布局。

修复方案:改用bounds并确保布局完成

修改UIButton的typeMain()扩展:

extension UIButton {
    func typeMain() {
        self.translatesAutoresizingMaskIntoConstraints = false
        let height = UIScreen.main.bounds.height * 0.07
        let width = UIScreen.main.bounds.width * 0.9
        self.heightAnchor.constraint(equalToConstant: height).isActive = true
        self.widthAnchor.constraint(equalToConstant: width).isActive = true
        
        // 强制更新布局,确保bounds正确
        self.layoutIfNeeded()
        
        // 给titleLabel设置字符间距(修正之前的笔误)
        self.titleLabel?.addCharacterSpacing()
        
        self.tintColor = UIColor.white
        let color = UIColor(red: 11/255, green: 95/255, blue: 244/255, alpha: 1)
        let sndColor = UIColor(red: 106/255, green: 178/255, blue: 255/255, alpha: 1)
        
        // 用bounds代替frame,因为bounds是按钮自身的尺寸,不受父视图布局影响
        self.layer.cornerRadius = self.bounds.size.height / 5.0
        self.applyGradient(colours: [color, sndColor], locations: [0.0, 1.0])
        
        let shadowSize : CGFloat = 2.0
        self.layer.shadowColor = UIColor(red: 106/255, green: 178/255, blue: 255/255, alpha: 1).cgColor
        self.layer.shadowOffset = CGSize(width: 0.0, height: 0.0)
        self.layer.shadowOpacity = 0.4
        
        // 同样用bounds创建阴影路径
        let shadowPath = UIBezierPath(rect: CGRect(x: -shadowSize / 2, y: shadowSize, width: self.bounds.size.width + shadowSize, height: self.bounds.size.height + shadowSize))
        self.layer.shadowPath = shadowPath.cgPath
        self.layer.shadowRadius = 5
        self.layer.masksToBounds = false
    }
}

3. 额外注意点

  • 确保你的applyGradient方法是正确的(你代码里没贴出来,假设它能正常工作);
  • 如果按钮的布局会动态变化(比如旋转屏幕),建议把圆角、阴影路径的代码移到layoutSubviews()里重写,确保样式始终正确。

这样修改后,你再调用loadingButton.typeMain(),然后触发showLoading(),标题就会被正确清空,加载指示器也会居中显示了。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.13 08:10:31