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
相关产品推荐
相关产品推荐

