如何在含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
相关产品推荐
相关产品推荐

