Swift技术问题:如何在CardButton中实现NSAttributedString横竖居中?
嘿,我来帮你搞定这个问题!你的思路方向是对的,但几个关键步骤的时机和实现方式搞错了,才导致containerView消失不见。咱们一步步拆解问题,再给出靠谱的解决方案。
先说说你代码里的核心问题
误用了
draw方法draw(_ rect: CGRect)是用来直接绘制图形/文本到当前视图的上下文里的,根本不是用来添加子视图和设置AutoLayout的地方。AutoLayout约束需要视图经历完整的布局周期才能生效,而你在draw里加完约束后,没有触发布局,containerView的bounds还是默认的CGRect.zero,自然看不到。sizeToFit时机完全错了
你先调用了stringToDraw.draw(in: containerView.bounds),但这时候containerView的尺寸还是空的,等于没画到任何可见区域;之后再调用sizeToFit,空视图也没法根据内容调整尺寸,完全没用。直接
draw文本的方式没必要
要显示NSAttributedString,用UILabel会省心太多——它自带文本居中、自动适配尺寸的能力,不需要手动处理绘制逻辑。
解决方案一:用UILabel承载表情(推荐,灵活易扩展)
把子视图的创建、约束配置放到视图初始化方法里,用UILabel来显示富文本,让AutoLayout正常工作:
class CardButton: UIButton { // 把表情标签作为属性,避免重复创建 private let symbolLabel = UILabel() // 假设这是你的业务属性,用来传递表情和字体大小 var symbol: String = "" { didSet { updateSymbolContent() } } var symbolFontSize: CGFloat = 24 { didSet { updateSymbolContent() } } override init(frame: CGRect) { super.init(frame: frame) setupSymbolLabel() } required init?(coder: NSCoder) { super.init(coder: coder) setupSymbolLabel() } private func setupSymbolLabel() { // 配置label基础属性 symbolLabel.textAlignment = .center symbolLabel.backgroundColor = .green // 调试用,之后可以去掉 symbolLabel.translatesAutoresizingMaskIntoConstraints = false addSubview(symbolLabel) // 设置约束:横竖居中,同时给button留边距防止表情溢出 NSLayoutConstraint.activate([ symbolLabel.centerXAnchor.constraint(equalTo: centerXAnchor), symbolLabel.centerYAnchor.constraint(equalTo: centerYAnchor), symbolLabel.leadingAnchor.constraint(greaterThanOrEqualTo: leadingAnchor, constant: 8), symbolLabel.trailingAnchor.constraint(lessThanOrEqualTo: trailingAnchor, constant: -8), symbolLabel.topAnchor.constraint(greaterThanOrEqualTo: topAnchor, constant: 8), symbolLabel.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor, constant: -8) ]) } private func updateSymbolContent() { // 生成富文本并赋值给label let attributedString = attributedString(symbol, fontSize: symbolFontSize) symbolLabel.attributedText = attributedString } // 你的自定义富文本生成方法 private func attributedString(_ text: String, fontSize: CGFloat) -> NSAttributedString { let attributes: [NSAttributedString.Key: Any] = [ .font: UIFont.systemFont(ofSize: fontSize) // 可以添加颜色、字间距等其他属性 ] return NSAttributedString(string: text, attributes: attributes) } // 如果需要自定义button背景,重写draw方法(只处理绘制,别碰子视图) override func draw(_ rect: CGRect) { super.draw(rect) // 这里可以绘制button的边框、背景色等 } }
解决方案二:直接在Button的draw方法里绘制表情(轻量高效)
如果你的需求只是显示单个表情,不需要子视图,也可以直接在draw方法里计算文本位置,实现横竖居中:
class CardButton: UIButton { var symbol: String = "" { didSet { setNeedsDisplay() } // 触发重绘 } var symbolFontSize: CGFloat = 24 { didSet { setNeedsDisplay() } } override func draw(_ rect: CGRect) { super.draw(rect) let attributedString = attributedString(symbol, fontSize: symbolFontSize) // 计算文本的实际尺寸 let textBounds = attributedString.boundingRect( with: rect.size, options: [.usesLineFragmentOrigin, .usesFontLeading], context: nil ) // 计算居中的绘制起点 let originX = (rect.width - textBounds.width) / 2 let originY = (rect.height - textBounds.height) / 2 let drawRect = CGRect(x: originX, y: originY, width: textBounds.width, height: textBounds.height) // 绘制文本 attributedString.draw(in: drawRect) } private func attributedString(_ text: String, fontSize: CGFloat) -> NSAttributedString { let attributes: [NSAttributedString.Key: Any] = [ .font: UIFont.systemFont(ofSize: fontSize) ] return NSAttributedString(string: text, attributes: attributes) } }
最后总结
- 第一种方案用UILabel,适合后续可能扩展样式的场景(比如加阴影、改变颜色);
- 第二种方案直接绘制,更轻量,适合仅显示单个表情的简单需求;
- 核心问题就是你把AutoLayout和子视图的逻辑放到了
draw方法里,把这些逻辑移到正确的视图生命周期方法(比如init)或者直接用绘制方式,就能解决消失的问题。
内容的提问来源于stack exchange,提问作者Peter Ševčík
相关产品推荐
相关产品推荐

