Add support for circular progress bar part 2

This commit is contained in:
Xi Zhang 2024-07-09 18:49:10 -04:00
parent 7fec6f540e
commit 1b0197ed2c
3 changed files with 90 additions and 43 deletions

View File

@ -11,11 +11,82 @@ import UIKit
@objcMembers open class CircularProgressBar: View, MVMCoreUIViewConstrainingProtocol {
var heightConstraint: NSLayoutConstraint?
weak var gradientLayer: CALayer?
var graphModel: CircularProgressBarModel? {
return model as? CircularProgressBarModel
}
private var progressLayer = CAShapeLayer()
private var tracklayer = CAShapeLayer()
private var labelLayer = CATextLayer()
var setProgressColor: UIColor = UIColor.red {
didSet {
progressLayer.strokeColor = setProgressColor.cgColor
}
}
var setTrackColor: UIColor = UIColor.white {
didSet {
tracklayer.strokeColor = setTrackColor.cgColor
}
}
/**
A path that consists of straight and curved line segments that you can render in your custom views.
Meaning our CAShapeLayer will now be drawn on the screen with the path we have specified here
*/
private var viewCGPath: CGPath? {
let width = graphModel?.diameter ?? 84
let height = width
return UIBezierPath(arcCenter: CGPoint(x: width / 2.0, y: height / 2.0),
radius: (width - 1.5)/2,
startAngle: CGFloat(-0.5 * Double.pi),
endAngle: CGFloat(1.5 * Double.pi), clockwise: true).cgPath
}
private func configureProgressViewToBeCircular() {
let lineWidth = graphModel?.lineWidth ?? 2.0
self.backgroundColor = UIColor.clear
self.drawShape(using: tracklayer, lineWidth: lineWidth)
self.drawShape(using: progressLayer, lineWidth: lineWidth)
}
private func drawShape(using shape: CAShapeLayer, lineWidth: CGFloat) {
shape.path = self.viewCGPath
shape.fillColor = UIColor.clear.cgColor
shape.lineWidth = lineWidth
self.layer.addSublayer(shape)
}
func setProgressWithAnimation(duration: TimeInterval, value: Float) {
let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.duration = duration
animation.fromValue = 0 //start animation at point 0
animation.toValue = value //end animation at point specified
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
progressLayer.strokeEnd = CGFloat(value)
progressLayer.add(animation, forKey: "animateCircle")
}
func drawLabel() {
let percent = graphModel?.percent ?? 0
let percentLen = percent > 9 ? 2 : 1
let attributedString = NSMutableAttributedString(string: String(percent) + "%")
attributedString.setAttributes([NSAttributedString.Key.font: MFStyler.fontBoldTitleLarge()], range: NSMakeRange(0, percentLen))
attributedString.setAttributes([NSAttributedString.Key.font: MFStyler.fontBoldBodyLarge()], range: NSMakeRange(percentLen, 1))
// Text layer
let width = graphModel?.diameter ?? 84
let height = width
labelLayer.string = attributedString
labelLayer.frame = CGRectMake((width - CGFloat(percentLen * 20))/2, (height - 30)/2, 60, 30)
self.layer.addSublayer(labelLayer)
}
// MARK: setup
open override func setupView() {
super.setupView()
@ -27,46 +98,21 @@ import UIKit
override open func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) {
super.set(with: model, delegateObject, additionalData)
guard let model = model as? CircularProgressBarModel else { return }
createGraphCircle(model)
}
class func getAngle(_ piValue: Double) -> Double {
return piValue / (2.0 * Double.pi) * 360.0
}
class func getPiValue(_ angle: Double) -> Double {
return angle / 360.0 * 2.0 * Double.pi
}
// MARK: circle
open func createGraphCircle(_ graphObject: CircularProgressBarModel) {
if let sublayers = layer.sublayers {
for sublayer in sublayers {
sublayer.removeAllAnimations()
sublayer.removeFromSuperlayer()
}
configureProgressViewToBeCircular()
if let color = model.color {
setProgressColor = color.uiColor
}
heightConstraint?.constant = graphObject.diameter
let gradient = CAGradientLayer()
gradient.type = .conic
gradient.startPoint = CGPoint(x: 0.5, y: 0.5)
gradient.endPoint = CGPoint(x: 0.5, y: 0.0)
gradient.frame = CGRect(x: 0, y: 0, width: graphObject.diameter, height: graphObject.diameter)
gradientLayer = gradient
layer.addSublayer(gradient)
if let backgroundColor = model.backgroundColor {
setTrackColor = backgroundColor.uiColor
}
let center = CGPoint(x: gradient.bounds.midX, y: gradient.bounds.midY)
let radius = (graphObject.diameter - graphObject.lineWidth) / 2.0
let path = UIBezierPath(arcCenter: center, radius: radius, startAngle: (3 / 2 * .pi), endAngle: -(1 / 2 * .pi), clockwise: false)
let mask = CAShapeLayer()
mask.fillColor = UIColor.clear.cgColor
mask.strokeColor = UIColor.white.cgColor
mask.lineWidth = graphObject.lineWidth
mask.path = path.cgPath
gradient.mask = mask
setProgressWithAnimation(duration: 0.5, value: Float(graphModel?.percent ?? 0) / 100)
drawLabel()
}
//MARK: MVMCoreUIViewConstrainingProtocol
public func needsToBeConstrained() -> Bool {
return true

View File

@ -83,16 +83,16 @@ public class CircularProgressBarModel: MoleculeModelProtocol {
func updateSize() {
switch size {
case .small:
diameter = MFSizeObject(standardSize: 20)?.getValueBasedOnApplicationWidth() ?? 20
lineWidth = MFSizeObject(standardSize: 4)?.getValueBasedOnApplicationWidth() ?? 4
diameter = MFSizeObject(standardSize: 64)?.getValueBasedOnApplicationWidth() ?? 64
lineWidth = MFSizeObject(standardSize: 5)?.getValueBasedOnApplicationWidth() ?? 5
break
case .medium:
diameter = MFSizeObject(standardSize: 100)?.getValueBasedOnApplicationWidth() ?? 100
lineWidth = MFSizeObject(standardSize: 8)?.getValueBasedOnApplicationWidth() ?? 8
diameter = MFSizeObject(standardSize: 84)?.getValueBasedOnApplicationWidth() ?? 84
lineWidth = MFSizeObject(standardSize: 5)?.getValueBasedOnApplicationWidth() ?? 5
break
case .large:
diameter = MFSizeObject(standardSize: 180)?.getValueBasedOnApplicationWidth() ?? 180
lineWidth = MFSizeObject(standardSize: 12)?.getValueBasedOnApplicationWidth() ?? 12
diameter = MFSizeObject(standardSize: 124)?.getValueBasedOnApplicationWidth() ?? 124
lineWidth = MFSizeObject(standardSize: 5)?.getValueBasedOnApplicationWidth() ?? 5
break
}
}

View File

@ -67,6 +67,7 @@ open class CoreUIModelMapping: ModelMapping {
ModelRegistry.register(handler: LoadImageView.self, for: ImageViewModel.self)
ModelRegistry.register(handler: Line.self, for: LineModel.self)
ModelRegistry.register(handler: Wheel.self, for: WheelModel.self)
ModelRegistry.register(handler: CircularProgressBar.self, for: CircularProgressBarModel.self)
ModelRegistry.register(handler: Toggle.self, for: ToggleModel.self)
ModelRegistry.register(handler: CheckboxLabel.self, for: CheckboxLabelModel.self)
ModelRegistry.register(handler: Arrow.self, for: ArrowModel.self)