Refactor codes for circular progress UI.

This commit is contained in:
Xi Zhang 2024-07-11 13:12:29 -04:00
parent 1b0197ed2c
commit 20d818b2c2
2 changed files with 71 additions and 42 deletions

View File

@ -15,6 +15,10 @@ import UIKit
return model as? CircularProgressBarModel return model as? CircularProgressBarModel
} }
var viewWidth: CGFloat {
graphModel?.diameter ?? CGFloat(84)
}
private var progressLayer = CAShapeLayer() private var progressLayer = CAShapeLayer()
private var tracklayer = CAShapeLayer() private var tracklayer = CAShapeLayer()
private var labelLayer = CATextLayer() private var labelLayer = CATextLayer()
@ -25,18 +29,16 @@ import UIKit
} }
} }
var setTrackColor: UIColor = UIColor.white { var setTrackColor: UIColor = UIColor.lightGray {
didSet { didSet {
tracklayer.strokeColor = setTrackColor.cgColor tracklayer.strokeColor = setTrackColor.cgColor
} }
} }
/**
A path that consists of straight and curved line segments that you can render in your custom views. // A path with which CAShapeLayer will be drawn on the screen
Meaning our CAShapeLayer will now be drawn on the screen with the path we have specified here
*/
private var viewCGPath: CGPath? { private var viewCGPath: CGPath? {
let width = graphModel?.diameter ?? 84 let width = viewWidth
let height = width let height = width
return UIBezierPath(arcCenter: CGPoint(x: width / 2.0, y: height / 2.0), return UIBezierPath(arcCenter: CGPoint(x: width / 2.0, y: height / 2.0),
@ -45,9 +47,51 @@ import UIKit
endAngle: CGFloat(1.5 * Double.pi), clockwise: true).cgPath endAngle: CGFloat(1.5 * Double.pi), clockwise: true).cgPath
} }
// MARK: setup
override open func setupView() {
super.setupView()
heightConstraint = heightAnchor.constraint(equalToConstant: 0)
heightConstraint?.isActive = true
widthAnchor.constraint(equalTo: heightAnchor).isActive = true
}
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 }
// set background color
if let backgroundColor = model.backgroundColor {
self.backgroundColor = backgroundColor.uiColor
} else {
self.backgroundColor = UIColor.clear
}
configureProgressViewToBeCircular()
// set progress color
if let color = model.color {
setProgressColor = color.uiColor
} else {
setProgressColor = UIColor.red
}
// set track color
if let trackColor = model.trackColor {
setTrackColor = trackColor.uiColor
} else {
setProgressColor = UIColor.lightGray
}
// show circular progress view with animation.
showProgressWithAnimation(duration: 0.5, value: Float(graphModel?.percent ?? 0) / 100)
// show progress percentage label.
showProgressPercentage()
}
private func configureProgressViewToBeCircular() { private func configureProgressViewToBeCircular() {
let lineWidth = graphModel?.lineWidth ?? 2.0 let lineWidth = graphModel?.lineWidth ?? 5.0
self.backgroundColor = UIColor.clear
self.drawShape(using: tracklayer, lineWidth: lineWidth) self.drawShape(using: tracklayer, lineWidth: lineWidth)
self.drawShape(using: progressLayer, lineWidth: lineWidth) self.drawShape(using: progressLayer, lineWidth: lineWidth)
@ -60,7 +104,8 @@ import UIKit
self.layer.addSublayer(shape) self.layer.addSublayer(shape)
} }
func setProgressWithAnimation(duration: TimeInterval, value: Float) { // value range is [0,1]
private func showProgressWithAnimation(duration: TimeInterval, value: Float) {
let animation = CABasicAnimation(keyPath: "strokeEnd") let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.duration = duration animation.duration = duration
@ -71,47 +116,26 @@ import UIKit
progressLayer.add(animation, forKey: "animateCircle") progressLayer.add(animation, forKey: "animateCircle")
} }
func drawLabel() { private func showProgressPercentage() {
let percent = graphModel?.percent ?? 0 let percent = graphModel?.percent ?? 0
let percentLen = percent > 9 ? 2 : 1 let percentLen = percent > 9 ? 2 : 1
// configure attributed string for progress percentage.
let attributedString = NSMutableAttributedString(string: String(percent) + "%") let attributedString = NSMutableAttributedString(string: String(percent) + "%")
// percent value
attributedString.setAttributes([NSAttributedString.Key.font: MFStyler.fontBoldTitleLarge()], range: NSMakeRange(0, percentLen)) attributedString.setAttributes([NSAttributedString.Key.font: MFStyler.fontBoldTitleLarge()], range: NSMakeRange(0, percentLen))
// % symbol
attributedString.setAttributes([NSAttributedString.Key.font: MFStyler.fontBoldBodyLarge()], range: NSMakeRange(percentLen, 1)) attributedString.setAttributes([NSAttributedString.Key.font: MFStyler.fontBoldBodyLarge()], range: NSMakeRange(percentLen, 1))
// Text layer // show progress percentage in a text layer
let width = graphModel?.diameter ?? 84 let width = viewWidth
let height = width let height = width
labelLayer.string = attributedString labelLayer.string = attributedString
labelLayer.frame = CGRectMake((width - CGFloat(percentLen * 20))/2, (height - 30)/2, 60, 30) labelLayer.frame = CGRectMake((width - CGFloat(percentLen * 20))/2, (height - 30)/2, 60, 30)
self.layer.addSublayer(labelLayer) self.layer.addSublayer(labelLayer)
} }
// MARK: setup
open override func setupView() {
super.setupView()
heightConstraint = heightAnchor.constraint(equalToConstant: 0)
heightConstraint?.isActive = true
widthAnchor.constraint(equalTo: heightAnchor).isActive = true
}
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 }
configureProgressViewToBeCircular()
if let color = model.color {
setProgressColor = color.uiColor
}
if let backgroundColor = model.backgroundColor {
setTrackColor = backgroundColor.uiColor
}
setProgressWithAnimation(duration: 0.5, value: Float(graphModel?.percent ?? 0) / 100)
drawLabel()
}
//MARK: MVMCoreUIViewConstrainingProtocol //MARK: MVMCoreUIViewConstrainingProtocol
public func needsToBeConstrained() -> Bool { public func needsToBeConstrained() -> Bool {

View File

@ -22,9 +22,10 @@ public class CircularProgressBarModel: MoleculeModelProtocol {
public var diameter: CGFloat = 84 public var diameter: CGFloat = 84
public var lineWidth: CGFloat = 5 public var lineWidth: CGFloat = 5
public var color: Color? public var color: Color?
public var backgroundColor: Color? public var trackColor: Color?
public var percent: Int? public var percent: Int?
public var backgroundColor: Color? = Color(uiColor: UIColor.clear)
public init() { public init() {
updateSize() updateSize()
} }
@ -35,8 +36,9 @@ public class CircularProgressBarModel: MoleculeModelProtocol {
case diameter case diameter
case lineWidth case lineWidth
case color case color
case backgroundColor case trackColor
case percent case percent
case backgroundColor
case moleculeName case moleculeName
} }
@ -58,8 +60,10 @@ public class CircularProgressBarModel: MoleculeModelProtocol {
} }
color = try typeContainer.decodeIfPresent(Color.self, forKey: .color) color = try typeContainer.decodeIfPresent(Color.self, forKey: .color)
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) trackColor = try typeContainer.decodeIfPresent(Color.self, forKey: .trackColor)
percent = try typeContainer.decodeIfPresent(Int.self, forKey: .percent) percent = try typeContainer.decodeIfPresent(Int.self, forKey: .percent)
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
} }
public func encode(to encoder: Encoder) throws { public func encode(to encoder: Encoder) throws {
@ -69,9 +73,10 @@ public class CircularProgressBarModel: MoleculeModelProtocol {
try container.encode(size, forKey: .size) try container.encode(size, forKey: .size)
try container.encode(diameter, forKey: .diameter) try container.encode(diameter, forKey: .diameter)
try container.encode(lineWidth, forKey: .lineWidth) try container.encode(lineWidth, forKey: .lineWidth)
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) try container.encodeIfPresent(trackColor, forKey: .trackColor)
try container.encodeIfPresent(color, forKey: .color) try container.encodeIfPresent(color, forKey: .color)
try container.encodeIfPresent(percent, forKey: .percent) try container.encodeIfPresent(percent, forKey: .percent)
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
} }
func getCGColorsFromArray(_ colorArray: [String]) -> [Color] { func getCGColorsFromArray(_ colorArray: [String]) -> [Color] {