diff --git a/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/CircularProgressPreview.swift b/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/CircularProgressPreview.swift index 436dcb68..ef98cd9d 100644 --- a/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/CircularProgressPreview.swift +++ b/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/CircularProgressPreview.swift @@ -4,12 +4,8 @@ import UIKit struct CircularProgressPreview: View { @State private var model = Self.initialModel - @State private var currentValue: CGFloat = Self.initialValue - private let circularProgress = UKCircularProgress( - initialValue: Self.initialValue, - model: Self.initialModel - ) + private let circularProgress = UKCircularProgress(model: Self.initialModel) private let timer = Timer .publish(every: 0.5, on: .main, in: .common) @@ -21,18 +17,14 @@ struct CircularProgressPreview: View { self.circularProgress .preview .onAppear { - self.circularProgress.currentValue = Self.initialValue self.circularProgress.model = Self.initialModel } .onChange(of: model) { newModel in self.circularProgress.model = newModel } - .onChange(of: self.currentValue) { newValue in - self.circularProgress.currentValue = newValue - } } PreviewWrapper(title: "SwiftUI") { - SUCircularProgress(currentValue: self.currentValue, model: self.model) + SUCircularProgress(model: self.model) } Form { ComponentColorPicker(selection: self.$model.color) @@ -54,28 +46,25 @@ struct CircularProgressPreview: View { SizePicker(selection: self.$model.size) } .onReceive(self.timer) { _ in - if self.currentValue < self.model.maxValue { + if self.model.currentValue < self.model.maxValue { let step = (self.model.maxValue - self.model.minValue) / 100 - self.currentValue = min( + self.model.currentValue = min( self.model.maxValue, - self.currentValue + CGFloat(Int.random(in: 1...20)) * step + self.model.currentValue + CGFloat(Int.random(in: 1...20)) * step ) } else { - self.currentValue = self.model.minValue + self.model.currentValue = self.model.minValue } - self.model.label = "\(Int(self.currentValue))%" + self.model.label = "\(Int(self.model.currentValue))%" } } } // MARK: - Helpers - private static var initialValue: Double { - return 0.0 - } - private static var initialModel = CircularProgressVM { $0.label = "0%" + $0.currentValue = 0.0 } } diff --git a/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/ProgressBarPreview.swift b/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/ProgressBarPreview.swift index c7b5133e..b01b6726 100644 --- a/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/ProgressBarPreview.swift +++ b/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/ProgressBarPreview.swift @@ -4,10 +4,9 @@ import UIKit struct ProgressBarPreview: View { @State private var model = Self.initialModel - @State private var currentValue: CGFloat = Self.initialValue - - private let progressBar = UKProgressBar(initialValue: Self.initialValue, model: Self.initialModel) - + + private let progressBar = UKProgressBar(model: Self.initialModel) + private let timer = Timer .publish(every: 0.5, on: .main, in: .common) .autoconnect() @@ -18,7 +17,6 @@ struct ProgressBarPreview: View { self.progressBar .preview .onAppear { - self.progressBar.currentValue = self.currentValue self.progressBar.model = Self.initialModel } .onChange(of: self.model) { newValue in @@ -26,7 +24,7 @@ struct ProgressBarPreview: View { } } PreviewWrapper(title: "SwiftUI") { - SUProgressBar(currentValue: self.currentValue, model: self.model) + SUProgressBar(model: self.model) } Form { ComponentColorPicker(selection: self.$model.color) @@ -42,25 +40,20 @@ struct ProgressBarPreview: View { } } .onReceive(self.timer) { _ in - if self.currentValue < self.model.maxValue { + if self.model.currentValue < self.model.maxValue { let step = (self.model.maxValue - self.model.minValue) / 100 - self.currentValue = min( + self.model.currentValue = min( self.model.maxValue, - self.currentValue + CGFloat(Int.random(in: 1...20)) * step + self.model.currentValue + CGFloat(Int.random(in: 1...20)) * step ) } else { - self.currentValue = self.model.minValue + self.model.currentValue = self.model.minValue } - - self.progressBar.currentValue = self.currentValue } } // MARK: - Helpers - private static var initialValue: Double { - return 0.0 - } private static var initialModel: ProgressBarVM { return .init() } diff --git a/Sources/ComponentsKit/Components/CircularProgress/Models/CircularProgressVM.swift b/Sources/ComponentsKit/Components/CircularProgress/Models/CircularProgressVM.swift index 768c487e..3422d75b 100644 --- a/Sources/ComponentsKit/Components/CircularProgress/Models/CircularProgressVM.swift +++ b/Sources/ComponentsKit/Components/CircularProgress/Models/CircularProgressVM.swift @@ -7,6 +7,11 @@ public struct CircularProgressVM: ComponentVM { /// Defaults to `.accent`. public var color: ComponentColor = .accent + /// The current value of the circular progress. + /// + /// Defaults to `0`. + public var currentValue: CGFloat = 0 + /// The font used for the circular progress label text. public var font: UniversalFont? @@ -103,6 +108,13 @@ extension CircularProgressVM { } extension CircularProgressVM { + var progress: CGFloat { + let range = self.maxValue - self.minValue + guard range > 0 else { return 0 } + let normalized = (self.currentValue - self.minValue) / range + return max(0, min(1, normalized)) + } + func progress(for currentValue: CGFloat) -> CGFloat { let range = self.maxValue - self.minValue guard range > 0 else { return 0 } @@ -123,6 +135,7 @@ extension CircularProgressVM { func shouldRecalculateProgress(_ oldModel: Self) -> Bool { return self.minValue != oldModel.minValue || self.maxValue != oldModel.maxValue + || self.currentValue != oldModel.currentValue } func shouldUpdateShape(_ oldModel: Self) -> Bool { return self.shape != oldModel.shape diff --git a/Sources/ComponentsKit/Components/CircularProgress/SUCircularProgress.swift b/Sources/ComponentsKit/Components/CircularProgress/SUCircularProgress.swift index 993c4dee..392e4438 100644 --- a/Sources/ComponentsKit/Components/CircularProgress/SUCircularProgress.swift +++ b/Sources/ComponentsKit/Components/CircularProgress/SUCircularProgress.swift @@ -8,10 +8,10 @@ public struct SUCircularProgress: View { public var model: CircularProgressVM /// The current progress value. - public var currentValue: CGFloat + public var currentValue: CGFloat? private var progress: CGFloat { - self.model.progress(for: self.currentValue) + self.currentValue.map { self.model.progress(for: $0) } ?? self.model.progress } // MARK: - Initializer @@ -20,6 +20,7 @@ public struct SUCircularProgress: View { /// - Parameters: /// - currentValue: Current progress. /// - model: A model that defines the appearance properties. + @available(*, deprecated, message: "Set `currentValue` in the model instead.") public init( currentValue: CGFloat = 0, model: CircularProgressVM = .init() @@ -28,6 +29,13 @@ public struct SUCircularProgress: View { self.model = model } + /// Initializer. + /// - Parameters: + /// - model: A model that defines the appearance properties. + public init(model: CircularProgressVM) { + self.model = model + } + // MARK: - Body public var body: some View { diff --git a/Sources/ComponentsKit/Components/CircularProgress/UKCircularProgress.swift b/Sources/ComponentsKit/Components/CircularProgress/UKCircularProgress.swift index e68f5001..193d2a11 100644 --- a/Sources/ComponentsKit/Components/CircularProgress/UKCircularProgress.swift +++ b/Sources/ComponentsKit/Components/CircularProgress/UKCircularProgress.swift @@ -13,12 +13,16 @@ open class UKCircularProgress: UIView, UKComponent { } /// The current progress value. - public var currentValue: CGFloat { + public var currentValue: CGFloat? { didSet { self.updateProgress() } } + private var progress: CGFloat { + self.currentValue.map { self.model.progress(for: $0) } ?? self.model.progress + } + // MARK: - Subviews /// The shape layer responsible for rendering the background. @@ -42,6 +46,7 @@ open class UKCircularProgress: UIView, UKComponent { /// - Parameters: /// - initialValue: The initial progress value. Defaults to `0`. /// - model: The model that defines the appearance properties. + @available(*, deprecated, message: "Set `currentValue` in the model instead.") public init( initialValue: CGFloat = 0, model: CircularProgressVM = .init() @@ -55,6 +60,18 @@ open class UKCircularProgress: UIView, UKComponent { self.layout() } + /// Initializer. + /// - Parameters: + /// - model: The model that defines the appearance properties. + public init(model: CircularProgressVM) { + self.model = model + super.init(frame: .zero) + + self.setup() + self.style() + self.layout() + } + public required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -72,7 +89,7 @@ open class UKCircularProgress: UIView, UKComponent { } } - self.progressLayer.strokeEnd = self.model.progress(for: self.currentValue) + self.progressLayer.strokeEnd = self.progress self.label.text = self.model.label } @@ -132,7 +149,7 @@ open class UKCircularProgress: UIView, UKComponent { CATransaction.begin() CATransaction.setAnimationDuration(self.model.animationDuration) CATransaction.setAnimationTimingFunction(CAMediaTimingFunction(name: .linear)) - self.progressLayer.strokeEnd = self.model.progress(for: self.currentValue) + self.progressLayer.strokeEnd = self.progress CATransaction.commit() } diff --git a/Sources/ComponentsKit/Components/ProgressBar/Models/ProgressBarVM.swift b/Sources/ComponentsKit/Components/ProgressBar/Models/ProgressBarVM.swift index 902293e9..f5bb2e47 100644 --- a/Sources/ComponentsKit/Components/ProgressBar/Models/ProgressBarVM.swift +++ b/Sources/ComponentsKit/Components/ProgressBar/Models/ProgressBarVM.swift @@ -7,26 +7,29 @@ public struct ProgressBarVM: ComponentVM { /// Defaults to `.accent`. public var color: ComponentColor = .accent - /// The visual style of the progress bar component. - /// - /// Defaults to `.striped`. - public var style: Style = .striped - - /// The size of the progress bar. + /// The corner radius of the progress bar. /// /// Defaults to `.medium`. - public var size: ComponentSize = .medium + public var cornerRadius: ComponentRadius = .medium - /// The minimum value of the progress bar. - public var minValue: CGFloat = 0 + /// The current value of the progress bar. + public var currentValue: CGFloat = 0 /// The maximum value of the progress bar. public var maxValue: CGFloat = 100 - /// The corner radius of the progress bar. + /// The minimum value of the progress bar. + public var minValue: CGFloat = 0 + + /// The size of the progress bar. /// /// Defaults to `.medium`. - public var cornerRadius: ComponentRadius = .medium + public var size: ComponentSize = .medium + + /// The visual style of the progress bar component. + /// + /// Defaults to `.striped`. + public var style: Style = .striped /// Initializes a new instance of `ProgressBarVM` with default values. public init() {} @@ -139,6 +142,13 @@ extension ProgressBarVM { } extension ProgressBarVM { + var progress: CGFloat { + let range = self.maxValue - self.minValue + guard range > 0 else { return 0 } + let normalized = (self.currentValue - self.minValue) / range + return max(0, min(1, normalized)) + } + func progress(for currentValue: CGFloat) -> CGFloat { let range = self.maxValue - self.minValue guard range > 0 else { return 0 } diff --git a/Sources/ComponentsKit/Components/ProgressBar/SUProgressBar.swift b/Sources/ComponentsKit/Components/ProgressBar/SUProgressBar.swift index 8da78bd7..96a1e0cc 100644 --- a/Sources/ComponentsKit/Components/ProgressBar/SUProgressBar.swift +++ b/Sources/ComponentsKit/Components/ProgressBar/SUProgressBar.swift @@ -1,16 +1,16 @@ import SwiftUI -/// A SwiftUI component that displays a progress bar. +/// A SwiftUI component that visually represents the progress of a task or process using a horizontal bar. public struct SUProgressBar: View { // MARK: - Properties /// A model that defines the appearance properties. public var model: ProgressBarVM /// The current progress value. - public var currentValue: CGFloat + public var currentValue: CGFloat? private var progress: CGFloat { - self.model.progress(for: self.currentValue) + self.currentValue.map { self.model.progress(for: $0) } ?? self.model.progress } // MARK: - Initializer @@ -19,6 +19,7 @@ public struct SUProgressBar: View { /// - Parameters: /// - currentValue: The current progress value. /// - model: A model that defines the appearance properties. + @available(*, deprecated, message: "Set `currentValue` in the model instead.") public init( currentValue: CGFloat, model: ProgressBarVM = .init() @@ -27,6 +28,13 @@ public struct SUProgressBar: View { self.model = model } + /// Initializer. + /// - Parameters: + /// - model: A model that defines the appearance properties. + public init(model: ProgressBarVM) { + self.model = model + } + // MARK: - Body public var body: some View { diff --git a/Sources/ComponentsKit/Components/ProgressBar/UKProgressBar.swift b/Sources/ComponentsKit/Components/ProgressBar/UKProgressBar.swift index 7fcbf095..28943e6b 100644 --- a/Sources/ComponentsKit/Components/ProgressBar/UKProgressBar.swift +++ b/Sources/ComponentsKit/Components/ProgressBar/UKProgressBar.swift @@ -1,9 +1,9 @@ import AutoLayout import UIKit -/// A UIKit component that displays a progress bar. +/// A UIKit component that visually represents the progress of a task or process using a horizontal bar. open class UKProgressBar: UIView, UKComponent { - // MARK: - Properties + // MARK: - Public Properties /// A model that defines the appearance properties. public var model: ProgressBarVM { @@ -13,7 +13,7 @@ open class UKProgressBar: UIView, UKComponent { } /// The current progress value for the progress bar. - public var currentValue: CGFloat { + public var currentValue: CGFloat? { didSet { self.updateProgressWidthAndAppearance() } @@ -39,7 +39,7 @@ open class UKProgressBar: UIView, UKComponent { // MARK: - Private Properties private var progress: CGFloat { - self.model.progress(for: self.currentValue) + self.currentValue.map { self.model.progress(for: $0) } ?? self.model.progress } // MARK: - UIView Properties @@ -54,6 +54,7 @@ open class UKProgressBar: UIView, UKComponent { /// - Parameters: /// - initialValue: The initial progress value. Defaults to `0`. /// - model: A model that defines the appearance properties. + @available(*, deprecated, message: "Set `currentValue` in the model instead.") public init( initialValue: CGFloat = 0, model: ProgressBarVM = .init() @@ -67,6 +68,18 @@ open class UKProgressBar: UIView, UKComponent { self.layout() } + /// Initializer. + /// - Parameters: + /// - model: A model that defines the appearance properties. + public init(model: ProgressBarVM) { + self.model = model + super.init(frame: .zero) + + self.setup() + self.style() + self.layout() + } + public required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -138,9 +151,9 @@ open class UKProgressBar: UIView, UKComponent { self.setNeedsLayout() } - UIView.performWithoutAnimation { +// UIView.performWithoutAnimation { self.updateProgressWidthAndAppearance() - } +// } } private func updateProgressWidthAndAppearance() {