Search code examples
iosswiftuitableviewprogress-barswift5

Initializing an instance from Custom ProgressBar Class into UItableviewCell


I'm trying to create a circular progressBar with timer function in my UItableviewCell class. I have created a custom class timer, and I am using Jonni's progressBar template (Credit to Jonni Åkesson) from - https://github.com/innoj/CountdownTimer.

The only noticeable difference between my code and source template is that I am adding the Custom ProgressBar class UIView programmatically whereas the source template used IBOutlet to connect to the Custom ProgressBar Class.

My goal is to ensure that the CAShapeLayers (both actual and background layers are shown as per below image - Source: https://www.youtube.com/watch?v=-KwFvGVstyc

enter image description here

Below is my code and I am unable to see both front CAShapeLayer and background CAShapeLayer. I am suspecting a logical error when I initialize constant "progressBar".

Please advise if it is possible to utilize the Custom ProgressBar Class, instead of re-writing the entire code.

import Foundation
import UIKit
import IQKeyboardManagerSwift


class ActiveExerciseTableViewCell: UITableViewCell, UITextFieldDelegate {

    let progressBar: ProgressBar = {
        let progressBar = ProgressBar()
        return progressBar
    }()

    lazy var  activeExerciseTimerUIView: UIView = { [weak self] in
        let activeExerciseTimerUIView = UIView()
        activeExerciseTimerUIView.backgroundColor = .black
        activeExerciseTimerUIView.isUserInteractionEnabled = true
        activeExerciseTimerUIView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tapTimerView)))
        return activeExerciseTimerUIView
    }()

    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        
        contentView.addSubview(activeExerciseTimerUIView)

    }

    override func layoutSubviews() {
        super.layoutSubviews()

        setUpActiveExerciseUIViewLayout()

}

func setUpActiveExerciseUIViewLayout(){

activeExerciseTimerUIView.translatesAutoresizingMaskIntoConstraints = false
        activeExerciseTimerUIView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true
        
        activeExerciseTimerUIView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: (contentView.frame.height-tableviewContentViewTabBarHeight)*0.25).isActive = true
        activeExerciseTimerUIView.widthAnchor.constraint(equalToConstant: 225).isActive = true
        activeExerciseTimerUIView.heightAnchor.constraint(equalToConstant: 225).isActive = true
        activeExerciseTimerUIView.layer.cornerRadius = activeExerciseTimerUIView.frame.width/2

        
        progressBar.frame = CGRect(
            x: 0,
            y: 0,
            width: activeExerciseTimerUIView.frame.width,
            height: activeExerciseTimerUIView.frame.height)
        
        self.activeExerciseTimerUIView.addSubview(progressBar)
    
    }

Below is the Custom ProgressBar Class from source file.

import UIKit


class ProgressBar: UIView, CAAnimationDelegate {

fileprivate var animation = CABasicAnimation()
fileprivate var animationDidStart = false
fileprivate var timerDuration = 0

lazy var fgProgressLayer: CAShapeLayer = {
    let fgProgressLayer = CAShapeLayer()
    return fgProgressLayer
}()

lazy var bgProgressLayer: CAShapeLayer = {
    let bgProgressLayer = CAShapeLayer()
    return bgProgressLayer
}()


required init(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)!
    loadBgProgressBar()
    loadFgProgressBar()
}

override init(frame: CGRect) {
    super.init(frame: frame)
    loadBgProgressBar()
    loadFgProgressBar()
}


fileprivate func loadFgProgressBar() {
    
    let startAngle = CGFloat(-Double.pi / 2)
    let endAngle = CGFloat(3 * Double.pi / 2)
    let centerPoint = CGPoint(x: frame.width/2 , y: frame.height/2)
    let gradientMaskLayer = gradientMask()
    fgProgressLayer.path = UIBezierPath(arcCenter:centerPoint, radius: frame.width/2 - 30.0, startAngle:startAngle, endAngle:endAngle, clockwise: true).cgPath
    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
    
    gradientMaskLayer.mask = fgProgressLayer
    layer.addSublayer(gradientMaskLayer)
}


fileprivate func gradientMask() -> CAGradientLayer {
    let gradientLayer = CAGradientLayer()
    gradientLayer.frame = bounds
    gradientLayer.locations = [0.0, 1.0]
    let colorTop: AnyObject = CustomColor.lime.cgColor
    let colorBottom: AnyObject = CustomColor.lime.cgColor
    let arrayOfColors: [AnyObject] = [colorTop, colorBottom]
    gradientLayer.colors = arrayOfColors
    return gradientLayer
}


fileprivate func loadBgProgressBar() {
    
    let startAngle = CGFloat(-Double.pi / 2)
    let endAngle = CGFloat(3 * Double.pi / 2)
    let centerPoint = CGPoint(x: frame.width/2 , y: frame.height/2)
    let gradientMaskLayer = gradientMaskBg()
    bgProgressLayer.path = UIBezierPath(arcCenter:centerPoint, radius: frame.width/2 - 30.0, startAngle:startAngle, endAngle:endAngle, clockwise: true).cgPath
    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
    
    gradientMaskLayer.mask = bgProgressLayer
    layer.addSublayer(gradientMaskLayer)
}


fileprivate func gradientMaskBg() -> CAGradientLayer {
    let gradientLayer = CAGradientLayer()
    gradientLayer.frame = bounds
    gradientLayer.locations = [0.0, 1.0]
    let colorTop: AnyObject = CustomColor.strawberry.cgColor
    let colorBottom: AnyObject = CustomColor.strawberry.cgColor
    let arrayOfColors: [AnyObject] = [colorTop, colorBottom]
    gradientLayer.colors = arrayOfColors
    
    return gradientLayer
}

Solution

  • Resolve the issue by loading the background and foreground progress bars in layoutSubViews function.

    ProgressBar.swift
    
    import UIKit
    import Pulsator
    
    
    class ProgressBar: UIView, CAAnimationDelegate {
        
        fileprivate var animation = CABasicAnimation()
        fileprivate var animationDidStart = false
        fileprivate var updatedAnimationAdded = false
        fileprivate var updateExecuted = false
        fileprivate var totalTimerDuration = 0
        
        fileprivate var isProgressBarAdded:Bool = false
        fileprivate var barlineWidth: CGFloat = 8
        
    //    let pulsator = Pulsator()
        
    //    fileprivate var width: CGFloat
    //    fileprivate var height: CGFloat
    //
        lazy var fgProgressLayer: CAShapeLayer = {
            let fgProgressLayer = CAShapeLayer()
            return fgProgressLayer
        }()
        
        lazy var bgProgressLayer: CAShapeLayer = {
            let bgProgressLayer = CAShapeLayer()
            return bgProgressLayer
        }()
        
        required init(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)!
            loadBgProgressBar()
            loadFgProgressBar()
        }
    
        override init(frame: CGRect) {
            super.init(frame: frame)
    
        }
        
        override func layoutSubviews() {
            super.layoutSubviews()
            
            if self.frame.width > 0 && self.frame.height > 0 && isProgressBarAdded == false {
                loadBgProgressBar()
                loadFgProgressBar()
                
                isProgressBarAdded = true
            }
            
        }