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

为何在layoutSubviews中配置CircularProgressView控件无法动态更新?

问题分析:CircularProgressView在layoutSubviews中配置控件无法更新的原因

问题描述

开发测速应用时,自定义CircularProgressView包含居中的speedLabel,通过createCircularPath创建进度路径并在layoutSubviews中调用。在init中配置speedLabel时,测速过程中speedLabel文本和progressTrack能正常更新;但将speedLabel配置移到layoutSubviews后,两者均无法更新。尝试调整z轴层级、嵌套图层视图均无效。

配置在layoutSubviews时的核心代码:

import UIKit

class CircularProgressView: UIView {
    
    var progressLayer = CAShapeLayer()
    var trackLayer = CAShapeLayer()
    var speedLabel = UILabel()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        createCircularPath()
        configureSpeedLabel()
    }
    
    var progressColor = UIColor.white {
        didSet {
            progressLayer.strokeColor = progressColor.cgColor
        }
    }
    
    var trackColor = UIColor.white {
        didSet {
            trackLayer.strokeColor = trackColor.cgColor
        }
    }
    
    var speed = 0 {
        didSet {
            speedLabel.text = String(speed)
        }
    }
    
    fileprivate func configureSpeedLabel() {
        speedLabel.translatesAutoresizingMaskIntoConstraints = false
        speedLabel.text = String(speed)
        speedLabel.font = UIFont(name: "TimesNewRomanPSMT", size: 24)
        speedLabel.textColor = UIColor.black
        speedLabel.isOpaque = true
        self.addSubview(speedLabel)
        NSLayoutConstraint.activate([
            speedLabel.centerXAnchor.constraint(equalTo: self.centerXAnchor),
            speedLabel.centerYAnchor.constraint(equalTo: self.centerYAnchor)
        ])
    }
    fileprivate func createCircularPath() {
        self.backgroundColor = UIColor.clear
        self.layer.cornerRadius = self.frame.width/2
        let path = UIBezierPath(arcCenter: CGPoint(x: frame.width/2, y: frame.height/2), radius: (frame.width - 1.5)/2, startAngle: CGFloat(-0.5 * .pi), endAngle: CGFloat(1.5 * .pi), clockwise: true)
        trackLayer.path = path.cgPath
        trackLayer.fillColor = UIColor.clear.cgColor
        trackLayer.strokeColor = trackColor.cgColor
        trackLayer.opacity = 0.3
        trackLayer.lineWidth = 10.0
        trackLayer.strokeEnd = 1.0
        self.layer.addSublayer(trackLayer)

        progressLayer.path = path.cgPath
        progressLayer.fillColor = UIColor.clear.cgColor
        progressLayer.strokeColor = progressColor.cgColor
        progressLayer.lineWidth = 10.0
        progressLayer.strokeEnd = 0.0
        self.layer.addSublayer(progressLayer)
    }
}

核心原因拆解

1. 进度图层被频繁重置

layoutSubviews会在视图布局变化时多次触发(如设备旋转、父视图调整、测速时界面重绘等),而createCircularPath中每次都会将progressLayer.strokeEnd硬编码设为0.0。这意味着你测速时更新的进度值,会被layoutSubviews的调用强制重置为0,导致进度无法正常显示变化。

同时,每次调用createCircularPath都会重复执行addSublayer操作(尽管图层不会被重复添加),反复修改路径和属性会干扰正常的状态更新逻辑。

2. Label重复添加与约束冲突

configureSpeedLabel每次在layoutSubviews中调用时,都会执行self.addSubview(speedLabel)——即使Label已经是当前视图的子视图,UIKit不会重复添加,但每次都会重新激活约束,引发约束重复激活冲突,导致Label布局失效,无法正常显示或更新文本。

另外,每次调用configureSpeedLabel都会将speedLabel.text设为当前speed值,若layoutSubviews在speed更新后触发,会覆盖之前设置的文本,进一步加剧显示异常。

修复建议

调整图层初始化逻辑

将图层的创建、添加逻辑移到init方法中,仅在layoutSubviews中更新路径(适配视图大小变化),不重置strokeEnd属性:

override init(frame: CGRect) {
    super.init(frame: frame)
    setupLayers()
}

required init?(coder: NSCoder) {
    super.init(coder: coder)
    setupLayers()
}

private func setupLayers() {
    self.backgroundColor = UIColor.clear
    let path = UIBezierPath(arcCenter: CGPoint(x: frame.width/2, y: frame.height/2), radius: (frame.width - 1.5)/2, startAngle: CGFloat(-0.5 * .pi), endAngle: CGFloat(1.5 * .pi), clockwise: true)
    
    // 初始化trackLayer
    trackLayer.path = path.cgPath
    trackLayer.fillColor = UIColor.clear.cgColor
    trackLayer.strokeColor = trackColor.cgColor
    trackLayer.opacity = 0.3
    trackLayer.lineWidth = 10.0
    trackLayer.strokeEnd = 1.0
    self.layer.addSublayer(trackLayer)

    // 初始化progressLayer
    progressLayer.path = path.cgPath
    progressLayer.fillColor = UIColor.clear.cgColor
    progressLayer.strokeColor = progressColor.cgColor
    progressLayer.lineWidth = 10.0
    progressLayer.strokeEnd = 0.0
    self.layer.addSublayer(progressLayer)
    
    // 初始化Label
    configureSpeedLabel()
}

override func layoutSubviews() {
    super.layoutSubviews()
    // 仅更新图层路径,适配视图大小变化
    updateCircularPath()
}

private func updateCircularPath() {
    self.layer.cornerRadius = self.frame.width/2
    let path = UIBezierPath(arcCenter: CGPoint(x: frame.width/2, y: frame.height/2), radius: (frame.width - 1.5)/2, startAngle: CGFloat(-0.5 * .pi), endAngle: CGFloat(1.5 * .pi), clockwise: true)
    trackLayer.path = path.cgPath
    progressLayer.path = path.cgPath
}

避免重复添加Label和约束

将configureSpeedLabel移到init方法中执行一次,确保Label只被添加一次,约束只激活一次:

private func configureSpeedLabel() {
    speedLabel.translatesAutoresizingMaskIntoConstraints = false
    speedLabel.text = String(speed)
    speedLabel.font = UIFont(name: "TimesNewRomanPSMT", size: 24)
    speedLabel.textColor = UIColor.black
    speedLabel.isOpaque = true
    self.addSubview(speedLabel)
    NSLayoutConstraint.activate([
        speedLabel.centerXAnchor.constraint(equalTo: self.centerXAnchor),
        speedLabel.centerYAnchor.constraint(equalTo: self.centerYAnchor)
    ])
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 02:35:02