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
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
}
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
}
}