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

Swift技术问题:如何在CardButton中实现NSAttributedString横竖居中?

嘿,我来帮你搞定这个问题!你的思路方向是对的,但几个关键步骤的时机和实现方式搞错了,才导致containerView消失不见。咱们一步步拆解问题,再给出靠谱的解决方案。

先说说你代码里的核心问题

  1. 误用了draw方法
    draw(_ rect: CGRect)是用来直接绘制图形/文本到当前视图的上下文里的,根本不是用来添加子视图和设置AutoLayout的地方。AutoLayout约束需要视图经历完整的布局周期才能生效,而你在draw里加完约束后,没有触发布局,containerView的bounds还是默认的CGRect.zero,自然看不到。

  2. sizeToFit时机完全错了
    你先调用了stringToDraw.draw(in: containerView.bounds),但这时候containerView的尺寸还是空的,等于没画到任何可见区域;之后再调用sizeToFit,空视图也没法根据内容调整尺寸,完全没用。

  3. 直接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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.11 09:20:43