UITableViewCell中代码初始化自定义ProgressBar不显示CAShapeLayer问题咨询
iOS UITableViewCell 纯代码添加圆形计时ProgressBar不显示修复方案
结论
你可以直接复用现有自定义ProgressBar类,无需重写全部代码,不显示的核心原因是ProgressBar内部图层计算时使用了初始的零值frame,同时布局逻辑存在冗余。
问题根因
- 你初始化ProgressBar时调用无参数构造方法,此时ProgressBar的frame为
CGRectZero,内部执行loadBgProgressBar、loadFgProgressBar方法时,计算圆弧中心、半径、渐变图层frame都用了当时的零值尺寸,所以所有图层都没有可见内容,即使后续修改了ProgressBar的frame,内部图层也不会自动更新。 setUpActiveExerciseUIViewLayout方法在每次layoutSubviews回调时都会执行,会重复将ProgressBar添加到父视图,造成视图冗余。activeExerciseTimerUIView刚添加约束时frame还未生效,直接取frame.width设置圆角会不生效。
修复步骤
1. 修改业务代码(ActiveExerciseTableViewCell类)
将布局逻辑从layoutSubviews移到init方法中,使用AutoLayout约束ProgressBar,避免重复添加视图,修改后代码如下:
import Foundation import UIKit import IQKeyboardManagerSwift class ActiveExerciseTableViewCell: UITableViewCell, UITextFieldDelegate { let progressBar: ProgressBar = ProgressBar() lazy var activeExerciseTimerUIView: UIView = { [weak self] in let view = UIView() view.backgroundColor = .black view.isUserInteractionEnabled = true view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tapTimerView))) return view }() override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) setupSubviews() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func setupSubviews() { contentView.addSubview(activeExerciseTimerUIView) activeExerciseTimerUIView.addSubview(progressBar) // 开启AutoLayout activeExerciseTimerUIView.translatesAutoresizingMaskIntoConstraints = false progressBar.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ activeExerciseTimerUIView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor), activeExerciseTimerUIView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: (contentView.frame.height - tableviewContentViewTabBarHeight)*0.25), activeExerciseTimerUIView.widthAnchor.constraint(equalToConstant: 225), activeExerciseTimerUIView.heightAnchor.constraint(equalToConstant: 225), progressBar.leadingAnchor.constraint(equalTo: activeExerciseTimerUIView.leadingAnchor), progressBar.trailingAnchor.constraint(equalTo: activeExerciseTimerUIView.trailingAnchor), progressBar.topAnchor.constraint(equalTo: activeExerciseTimerUIView.topAnchor), progressBar.bottomAnchor.constraint(equalTo: activeExerciseTimerUIView.bottomAnchor) ]) } override func layoutSubviews() { super.layoutSubviews() // 布局完成后再设置圆角,此时frame已经生效 activeExerciseTimerUIView.layer.cornerRadius = activeExerciseTimerUIView.frame.width / 2 } // 原有其他方法保持不变 @objc private func tapTimerView() { // 原有点击逻辑 } }
2. 优化ProgressBar类(适配动态frame调整)
在ProgressBar类中重写layoutSubviews方法,当frame变化时自动更新内部图层的尺寸和路径,修改后代码如下:
import UIKit class ProgressBar: UIView, CAAnimationDelegate { fileprivate var animation = CABasicAnimation() fileprivate var animationDidStart = false fileprivate var timerDuration = 0 lazy var fgProgressLayer: CAShapeLayer = CAShapeLayer() lazy var bgProgressLayer: CAShapeLayer = CAShapeLayer() // 持有渐变图层方便后续更新frame private var fgGradientLayer: CAGradientLayer? private var bgGradientLayer: CAGradientLayer? required init(coder aDecoder: NSCoder) { super.init(coder: aDecoder)! commonInit() } override init(frame: CGRect) { super.init(frame: frame) commonInit() } private func commonInit() { loadBgProgressBar() loadFgProgressBar() } // 重写layoutSubviews,frame变化时更新图层 override func layoutSubviews() { super.layoutSubviews() updateLayerPaths() } private func updateLayerPaths() { let startAngle = CGFloat(-Double.pi / 2) let endAngle = CGFloat(3 * Double.pi / 2) let centerPoint = CGPoint(x: bounds.width/2 , y: bounds.height/2) let radius = bounds.width/2 - 30.0 // 更新背景图层 bgProgressLayer.path = UIBezierPath(arcCenter: centerPoint, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: true).cgPath bgGradientLayer?.frame = bounds // 更新前景图层 fgProgressLayer.path = UIBezierPath(arcCenter: centerPoint, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: true).cgPath fgGradientLayer?.frame = bounds } fileprivate func loadFgProgressBar() { fgProgressLayer.backgroundColor = UIColor.clear.cgColor fgProgressLayer.fillColor = nil fgProgressLayer.strokeColor = UIColor.black.cgColor fgProgressLayer.lineWidth = 4.0 fgProgressLayer.strokeStart = 0.0 fgProgressLayer.strokeEnd = 0.0 let gradientMaskLayer = gradientMask() fgGradientLayer = gradientMaskLayer gradientMaskLayer.mask = fgProgressLayer layer.addSublayer(gradientMaskLayer) } fileprivate func gradientMask() -> CAGradientLayer { let gradientLayer = CAGradientLayer() gradientLayer.frame = bounds gradientLayer.locations = [0.0, 1.0] gradientLayer.colors = [CustomColor.lime.cgColor, CustomColor.lime.cgColor] return gradientLayer } fileprivate func loadBgProgressBar() { bgProgressLayer.backgroundColor = UIColor.clear.cgColor bgProgressLayer.fillColor = nil bgProgressLayer.strokeColor = UIColor.black.cgColor bgProgressLayer.lineWidth = 4.0 bgProgressLayer.strokeStart = 0.0 bgProgressLayer.strokeEnd = 1.0 let gradientMaskLayer = gradientMaskBg() bgGradientLayer = gradientMaskLayer gradientMaskLayer.mask = bgProgressLayer layer.addSublayer(gradientMaskLayer) } fileprivate func gradientMaskBg() -> CAGradientLayer { let gradientLayer = CAGradientLayer() gradientLayer.frame = bounds gradientLayer.locations = [0.0, 1.0] gradientLayer.colors = [CustomColor.strawberry.cgColor, CustomColor.strawberry.cgColor] return gradientLayer } // 原有其他计时相关逻辑保持不变 }
完成以上修改后即可正常显示双层圆形进度条。
内容的提问来源于stack exchange,提问作者Michael
相关产品推荐
相关产品推荐

