diff --git a/MVMCoreUI/Atomic/Atoms/Views/CircularProgressBar.swift b/MVMCoreUI/Atomic/Atoms/Views/CircularProgressBar.swift index 9383425a..df5fe946 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/CircularProgressBar.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/CircularProgressBar.swift @@ -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 diff --git a/MVMCoreUI/Atomic/Atoms/Views/CircularProgressBarModel.swift b/MVMCoreUI/Atomic/Atoms/Views/CircularProgressBarModel.swift index 671607b1..3a865b62 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/CircularProgressBarModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/CircularProgressBarModel.swift @@ -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 } } diff --git a/MVMCoreUI/OtherHandlers/CoreUIModelMapping.swift b/MVMCoreUI/OtherHandlers/CoreUIModelMapping.swift index 22f65ade..d310a126 100644 --- a/MVMCoreUI/OtherHandlers/CoreUIModelMapping.swift +++ b/MVMCoreUI/OtherHandlers/CoreUIModelMapping.swift @@ -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)