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

如何在含CAShapeLayer的自定义View中添加刻度标签并解决崩溃

问题分析与解决方案

初始化添加UILabel崩溃的原因

在自定义UIView的init系列方法(如init(frame:)或init?(coder:))中直接添加子视图,会因视图未完成自身初始化流程、或尚未被添加到父视图,导致视图层级遍历异常,触发view.superview is nil during traversal错误。

正确用UILabel实现仪表盘标签的方式

方式1:在layoutSubviews中管理标签

layoutSubviews会在视图布局确定后调用,此时视图frame已明确,适合计算标签位置:

class ArcView: UIView {
    private var scaleLabels = [UILabel]()
    
    override func layoutSubviews() {
        super.layoutSubviews()
        
        // 清除旧标签,避免重复添加
        scaleLabels.forEach { $0.removeFromSuperview() }
        scaleLabels.removeAll()
        
        let labelCount = 8 // 仪表盘刻度总数
        let labelRadius = min(bounds.width, bounds.height) / 2 - 20 // 标签到中心的距离(留边距)
        let centerPoint = CGPoint(x: bounds.midX, y: bounds.midY)
        
        for index in 0..<labelCount {
            // 从顶部开始计算角度,适配仪表盘布局
            let angle = CGFloat(index) * (2 * .pi / CGFloat(labelCount)) - .pi / 2
            
            // 计算标签中心坐标
            let labelX = centerPoint.x + labelRadius * cos(angle)
            let labelY = centerPoint.y + labelRadius * sin(angle)
            
            let label = UILabel()
            label.text = "\(index * 45)" // 示例刻度值
            label.font = UIFont.systemFont(ofSize: 12)
            label.textColor = .black
            label.sizeToFit()
            label.center = CGPoint(x: labelX, y: labelY)
            
            addSubview(label)
            scaleLabels.append(label)
        }
    }
    
    // 你的圆弧绘制逻辑
    override func draw(_ rect: CGRect) {
        // ... 原有圆弧代码
    }
}

方式2:在视图加入父视图后初始化标签

重写didMoveToSuperview(),确保视图已有父视图后再添加标签:

override func didMoveToSuperview() {
    super.didMoveToSuperview()
    guard superview != nil else { return }
    setupScaleLabels()
}

private func setupScaleLabels() {
    // 复制上述layoutSubviews中的标签创建逻辑即可
}

高性能替代方案:用CoreText直接绘制文本

若刻度数量较多,添加大量UILabel会增加视图层级开销,可直接在draw(_ rect:)中用CoreText绘制文本:

override func draw(_ rect: CGRect) {
    // 先绘制圆弧...
    
    let labelCount = 8
    let labelRadius = min(bounds.width, bounds.height) / 2 - 20
    let centerPoint = CGPoint(x: bounds.midX, y: bounds.midY)
    guard let context = UIGraphicsGetCurrentContext() else { return }
    
    for index in 0..<labelCount {
        let angle = CGFloat(index) * (2 * .pi / CGFloat(labelCount)) - .pi / 2
        let text = "\(index * 45)"
        
        // 计算文本绘制位置
        let textX = centerPoint.x + labelRadius * cos(angle)
        let textY = centerPoint.y + labelRadius * sin(angle)
        
        // 调整文本角度,使其垂直于刻度半径
        context.saveGState()
        context.translateBy(x: textX, y: textY)
        context.rotate(by: angle + .pi / 2)
        
        // 设置文本属性
        let textAttrs: [NSAttributedString.Key: Any] = [
            .font: UIFont.systemFont(ofSize: 12),
            .foregroundColor: UIColor.black
        ]
        let attributedText = NSAttributedString(string: text, attributes: textAttrs)
        
        // 让文本居中绘制
        let textSize = attributedText.size()
        attributedText.draw(at: CGPoint(x: -textSize.width/2, y: -textSize.height/2))
        
        context.restoreGState()
    }
}

总结

  • UILabel完全适合实现需求,只要避开初始化时直接添加的坑,选择layoutSubviews或didMoveToSuperview时机即可。
  • 刻度数量多的时候,CoreText绘制性能更优,也无需管理子视图。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 14:52:23