为何在layoutSubviews中配置CircularProgressView控件无法动态更新?
问题描述
开发测速应用时,自定义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

