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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.30 16:18:04