Add support for circular progress bar.

This commit is contained in:
Xi Zhang 2024-07-05 15:10:34 -04:00
parent fdab57200d
commit 7fec6f540e
3 changed files with 182 additions and 0 deletions

View File

@ -153,6 +153,8 @@
444FB7C32821B76B00DFE692 /* TitleLockupModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 444FB7C22821B76B00DFE692 /* TitleLockupModel.swift */; };
4457904E27ECE989002B1E1E /* UIImageRenderingMode+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4457904D27ECE989002B1E1E /* UIImageRenderingMode+Extension.swift */; };
4B002ACA2BD855EC009BC9C1 /* DateDropdownEntryFieldModel+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B002AC92BD855EC009BC9C1 /* DateDropdownEntryFieldModel+Extension.swift */; };
4B3408A22C3873B0003BFABF /* CircularProgressBarModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B3408A12C3873B0003BFABF /* CircularProgressBarModel.swift */; };
4B3408A42C3873E8003BFABF /* CircularProgressBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B3408A32C3873E8003BFABF /* CircularProgressBar.swift */; };
522679C123FE886900906CBA /* ListLeftVariableCheckboxAllTextAndLinks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 522679BF23FE886900906CBA /* ListLeftVariableCheckboxAllTextAndLinks.swift */; };
522679C223FE886900906CBA /* ListLeftVariableCheckboxAllTextAndLinksModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 522679C023FE886900906CBA /* ListLeftVariableCheckboxAllTextAndLinksModel.swift */; };
52267A0723FFE25000906CBA /* ListOneColumnFullWidthTextAllTextAndLinks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52267A0623FFE25000906CBA /* ListOneColumnFullWidthTextAllTextAndLinks.swift */; };
@ -770,6 +772,8 @@
444FB7C22821B76B00DFE692 /* TitleLockupModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TitleLockupModel.swift; sourceTree = "<group>"; };
4457904D27ECE989002B1E1E /* UIImageRenderingMode+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImageRenderingMode+Extension.swift"; sourceTree = "<group>"; };
4B002AC92BD855EC009BC9C1 /* DateDropdownEntryFieldModel+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DateDropdownEntryFieldModel+Extension.swift"; sourceTree = "<group>"; };
4B3408A12C3873B0003BFABF /* CircularProgressBarModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircularProgressBarModel.swift; sourceTree = "<group>"; };
4B3408A32C3873E8003BFABF /* CircularProgressBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircularProgressBar.swift; sourceTree = "<group>"; };
522679BF23FE886900906CBA /* ListLeftVariableCheckboxAllTextAndLinks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListLeftVariableCheckboxAllTextAndLinks.swift; sourceTree = "<group>"; };
522679C023FE886900906CBA /* ListLeftVariableCheckboxAllTextAndLinksModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListLeftVariableCheckboxAllTextAndLinksModel.swift; sourceTree = "<group>"; };
52267A0623FFE25000906CBA /* ListOneColumnFullWidthTextAllTextAndLinks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListOneColumnFullWidthTextAllTextAndLinks.swift; sourceTree = "<group>"; };
@ -2311,6 +2315,8 @@
0A7BAFA2232BE63400FB8E22 /* CheckboxLabel.swift */,
D28A838223CCBD3F00DFE4FC /* WheelModel.swift */,
943784F3236B77BB006A1E82 /* Wheel.swift */,
4B3408A12C3873B0003BFABF /* CircularProgressBarModel.swift */,
4B3408A32C3873E8003BFABF /* CircularProgressBar.swift */,
943784F4236B77BB006A1E82 /* WheelAnimationHandler.swift */,
0AE98BB623FF18E9004C5109 /* ArrowModel.swift */,
0AE98BB423FF18D2004C5109 /* Arrow.swift */,
@ -3005,6 +3011,7 @@
D29DF2EF21ECEAE1003B2FB9 /* MFFonts.m in Sources */,
D22479942316AE5E003FCCF9 /* NSLayoutConstraintExtension.swift in Sources */,
D2B18B94236214AD00A9AEDC /* NavigationController.swift in Sources */,
4B3408A42C3873E8003BFABF /* CircularProgressBar.swift in Sources */,
0A9D09222433796500D2E6C0 /* CarouselIndicator.swift in Sources */,
EA17584E2BC9895A00A5C0D9 /* ButtonIcon.swift in Sources */,
D29E28DA23D21AFA00ACEA85 /* StringAndMoleculeModel.swift in Sources */,
@ -3025,6 +3032,7 @@
AA1EC59924373994003D6F50 /* ListThreeColumnSpeedTestDivider.swift in Sources */,
AA37CBD52519072F0027344C /* Stars.swift in Sources */,
942C378E2412F5B60066E45E /* ModalMoleculeStackTemplate.swift in Sources */,
4B3408A22C3873B0003BFABF /* CircularProgressBarModel.swift in Sources */,
8D8067D32444473A00203BE8 /* ListRightVariablePriceChangeAllTextAndLinks.swift in Sources */,
8D4687E4242E2DF300802879 /* ListFourColumnDataUsageListItem.swift in Sources */,
D2874024249BA6F300BE950A /* MVMCoreUISplitViewController+Extension.swift in Sources */,

View File

@ -0,0 +1,75 @@
//
// CircularProgressBar.swift
// MVMCoreUI
//
// Created by Xi Zhang on 7/5/24.
// Copyright © 2024 Verizon Wireless. All rights reserved.
//
import UIKit
@objcMembers open class CircularProgressBar: View, MVMCoreUIViewConstrainingProtocol {
var heightConstraint: NSLayoutConstraint?
weak var gradientLayer: CALayer?
var graphModel: CircularProgressBarModel? {
return model as? CircularProgressBarModel
}
// 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 }
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()
}
}
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)
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
}
//MARK: MVMCoreUIViewConstrainingProtocol
public func needsToBeConstrained() -> Bool {
return true
}
}

View File

@ -0,0 +1,99 @@
//
// CircularProgressBarModel.swift
// MVMCoreUI
//
// Created by Xi Zhang on 7/5/24.
// Copyright © 2024 Verizon Wireless. All rights reserved.
//
import Foundation
public class CircularProgressBarModel: MoleculeModelProtocol {
public static var identifier: String = "circularProgress"
public var id: String = UUID().uuidString
public var size: GraphSize = .small {
didSet {
updateSize()
}
}
public var diameter: CGFloat = 84
public var lineWidth: CGFloat = 5
public var color: Color?
public var backgroundColor: Color?
public var percent: Int?
public init() {
updateSize()
}
private enum CodingKeys: String, CodingKey {
case id
case size
case diameter
case lineWidth
case color
case backgroundColor
case percent
case moleculeName
}
required public init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
id = try typeContainer.decodeIfPresent(String.self, forKey: .id) ?? UUID().uuidString
if let size = try typeContainer.decodeIfPresent(GraphSize.self, forKey: .size) {
self.size = size
}
updateSize()
if let diameter = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .diameter) {
self.diameter = diameter
}
if let lineWidth = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .lineWidth) {
self.lineWidth = lineWidth
}
color = try typeContainer.decodeIfPresent(Color.self, forKey: .color)
backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor)
percent = try typeContainer.decodeIfPresent(Int.self, forKey: .percent)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(moleculeName, forKey: .moleculeName)
try container.encode(size, forKey: .size)
try container.encode(diameter, forKey: .diameter)
try container.encode(lineWidth, forKey: .lineWidth)
try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor)
try container.encodeIfPresent(color, forKey: .color)
try container.encodeIfPresent(percent, forKey: .percent)
}
func getCGColorsFromArray(_ colorArray: [String]) -> [Color] {
return colorArray.map { (colorString) -> Color in
return Color(uiColor: UIColor.mfGet(forHex: colorString))
}
}
func updateSize() {
switch size {
case .small:
diameter = MFSizeObject(standardSize: 20)?.getValueBasedOnApplicationWidth() ?? 20
lineWidth = MFSizeObject(standardSize: 4)?.getValueBasedOnApplicationWidth() ?? 4
break
case .medium:
diameter = MFSizeObject(standardSize: 100)?.getValueBasedOnApplicationWidth() ?? 100
lineWidth = MFSizeObject(standardSize: 8)?.getValueBasedOnApplicationWidth() ?? 8
break
case .large:
diameter = MFSizeObject(standardSize: 180)?.getValueBasedOnApplicationWidth() ?? 180
lineWidth = MFSizeObject(standardSize: 12)?.getValueBasedOnApplicationWidth() ?? 12
break
}
}
}