-
Notifications
You must be signed in to change notification settings - Fork 11
SUProgressBar #43
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
SUProgressBar #43
Changes from all commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
b47ec3d
SUProgressBar and ProgressBarVM
VislovIvan ac97dea
Documentation
VislovIvan 04c3980
swiftlint fix
VislovIvan 1070027
.self added
VislovIvan b2ac453
code style fix
VislovIvan 4504026
ProgressBarPreview step fix
VislovIvan fd77126
padding fix
VislovIvan dd1158d
var fraction fix
VislovIvan 0ff5aea
stripes fix
VislovIvan 9b65fae
unused properties removed
VislovIvan 3cec343
added innerCornerRadius
VislovIvan 1f5ecd1
some minor fix
VislovIvan 4d508f6
automatic progress in preview
VislovIvan f21599a
fix contentPaddings
VislovIvan b6470ff
code style fix
VislovIvan 8a36501
fix self
VislovIvan a88172e
.onReceive code style fix
VislovIvan 40d1a5f
swiftlint fix
VislovIvan 4e8055d
minor code improvements
mikhailChelbaev c08d790
merge with dev
mikhailChelbaev File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
42 changes: 42 additions & 0 deletions
42
Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/ProgressBarPreview.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| import ComponentsKit | ||
| import SwiftUI | ||
| import UIKit | ||
|
|
||
| struct ProgressBarPreview: View { | ||
| @State private var model = ProgressBarVM() | ||
| @State private var currentValue: CGFloat = 0 | ||
| private let timer = Timer | ||
| .publish(every: 0.1, on: .main, in: .common) | ||
| .autoconnect() | ||
|
|
||
| var body: some View { | ||
| VStack { | ||
| PreviewWrapper(title: "SwiftUI") { | ||
| SUProgressBar(currentValue: self.$currentValue, model: self.model) | ||
| } | ||
| Form { | ||
| ComponentColorPicker(selection: self.$model.color) | ||
| ComponentRadiusPicker(selection: self.$model.cornerRadius) { | ||
| Text("Custom: 2px").tag(ComponentRadius.custom(2)) | ||
| } | ||
| SizePicker(selection: self.$model.size) | ||
| Picker("Style", selection: self.$model.style) { | ||
| Text("Light").tag(ProgressBarVM.Style.light) | ||
| Text("Filled").tag(ProgressBarVM.Style.filled) | ||
| Text("Striped").tag(ProgressBarVM.Style.striped) | ||
| } | ||
| } | ||
| } | ||
| .onReceive(self.timer) { _ in | ||
| if self.currentValue < self.model.maxValue { | ||
| self.currentValue += (self.model.maxValue - self.model.minValue) / 100 | ||
| } else { | ||
| self.currentValue = self.model.minValue | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| #Preview { | ||
| ProgressBarPreview() | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
10 changes: 10 additions & 0 deletions
10
Sources/ComponentsKit/ProgressBar/Models/ProgressBarStyle.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| import Foundation | ||
|
|
||
| extension ProgressBarVM { | ||
| /// Defines the visual styles for the progress bar component. | ||
| public enum Style { | ||
| case light | ||
| case filled | ||
| case striped | ||
| } | ||
| } |
148 changes: 148 additions & 0 deletions
148
Sources/ComponentsKit/ProgressBar/Models/ProgressBarVM.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,148 @@ | ||
| import SwiftUI | ||
|
|
||
| /// A model that defines the appearance properties for a a progress bar component. | ||
| public struct ProgressBarVM: ComponentVM { | ||
| /// The color of the progress bar. | ||
| /// | ||
| /// 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. | ||
| /// | ||
| /// Defaults to `.medium`. | ||
| public var size: ComponentSize = .medium | ||
|
|
||
| /// The minimum value of the progress bar. | ||
| public var minValue: CGFloat = 0 | ||
|
|
||
| /// The maximum value of the progress bar. | ||
| public var maxValue: CGFloat = 100 | ||
|
|
||
| /// The corner radius of the progress bar. | ||
| /// | ||
| /// Defaults to `.medium`. | ||
| public var cornerRadius: ComponentRadius = .medium | ||
|
|
||
| /// Initializes a new instance of `ProgressBarVM` with default values. | ||
| public init() {} | ||
| } | ||
|
|
||
| // MARK: - Shared Helpers | ||
|
|
||
| extension ProgressBarVM { | ||
| var innerBarPadding: CGFloat { | ||
| return 3 | ||
| } | ||
|
|
||
| var barHeight: CGFloat { | ||
| switch self.style { | ||
| case .light: | ||
| switch size { | ||
| case .small: | ||
| return 4 | ||
| case .medium: | ||
| return 8 | ||
| case .large: | ||
| return 12 | ||
| } | ||
| case .filled, .striped: | ||
| switch self.size { | ||
| case .small: | ||
| return 20 | ||
| case .medium: | ||
| return 32 | ||
| case .large: | ||
| return 42 | ||
| } | ||
| } | ||
| } | ||
|
|
||
| var computedCornerRadius: CGFloat { | ||
| switch self.cornerRadius { | ||
| case .none: | ||
| return 0.0 | ||
| case .small: | ||
| return self.barHeight / 3.5 | ||
| case .medium: | ||
| return self.barHeight / 3.0 | ||
| case .large: | ||
| return self.barHeight / 2.5 | ||
| case .full: | ||
| return self.barHeight / 2.0 | ||
| case .custom(let value): | ||
| return min(value, self.barHeight / 2) | ||
| } | ||
| } | ||
|
|
||
| var innerCornerRadius: CGFloat { | ||
| return max(0, self.computedCornerRadius - self.innerBarPadding) | ||
| } | ||
|
|
||
| var backgroundColor: UniversalColor { | ||
| switch style { | ||
| case .light: | ||
| return self.color.background | ||
| case .filled, .striped: | ||
| return self.color.main | ||
| } | ||
| } | ||
|
|
||
| var barColor: UniversalColor { | ||
| switch style { | ||
| case .light: | ||
| return self.color.main | ||
| case .filled, .striped: | ||
| return self.color.contrast | ||
| } | ||
| } | ||
|
|
||
| func shouldUpdateLayout(_ oldModel: Self) -> Bool { | ||
| return self.size != oldModel.size | ||
| } | ||
|
|
||
| private func stripesCGPath(in rect: CGRect) -> CGMutablePath { | ||
| let stripeWidth: CGFloat = 2 | ||
| let stripeSpacing: CGFloat = 4 | ||
| let stripeAngle: Angle = .degrees(135) | ||
|
|
||
| let path = CGMutablePath() | ||
| let step = stripeWidth + stripeSpacing | ||
| let radians = stripeAngle.radians | ||
| let dx = rect.height * tan(radians) | ||
| for x in stride(from: dx, through: rect.width + rect.height, by: step) { | ||
| let topLeft = CGPoint(x: x, y: 0) | ||
| let topRight = CGPoint(x: x + stripeWidth, y: 0) | ||
| let bottomLeft = CGPoint(x: x + dx, y: rect.height) | ||
| let bottomRight = CGPoint(x: x + stripeWidth + dx, y: rect.height) | ||
| path.move(to: topLeft) | ||
| path.addLine(to: topRight) | ||
| path.addLine(to: bottomRight) | ||
| path.addLine(to: bottomLeft) | ||
| path.closeSubpath() | ||
| } | ||
| return path | ||
| } | ||
|
|
||
| public func stripesPath(in rect: CGRect) -> Path { | ||
| return Path(self.stripesCGPath(in: rect)) | ||
| } | ||
|
|
||
| public func stripesBezierPath(in rect: CGRect) -> UIBezierPath { | ||
| return UIBezierPath(cgPath: self.stripesCGPath(in: rect)) | ||
| } | ||
| } | ||
|
|
||
| // MARK: - Validation | ||
|
|
||
| extension ProgressBarVM { | ||
| func validateMinMaxValues() { | ||
| if self.minValue > self.maxValue { | ||
| assertionFailure("Min value must be less than max value") | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| import SwiftUI | ||
|
|
||
| /// A SwiftUI component that displays a progress bar. | ||
| public struct SUProgressBar: View { | ||
| // MARK: - Properties | ||
|
|
||
| /// A model that defines the appearance properties. | ||
| public var model: ProgressBarVM | ||
| /// A binding to control the current value. | ||
| @Binding public var currentValue: CGFloat | ||
|
|
||
| private var progress: CGFloat { | ||
| let range = self.model.maxValue - self.model.minValue | ||
|
|
||
| guard range > 0 else { | ||
| return 0 | ||
| } | ||
|
|
||
| let progress = (self.currentValue - self.model.minValue) / range | ||
| return max(0, min(1, progress)) | ||
| } | ||
|
|
||
| // MARK: - Initializer | ||
|
|
||
| /// Initializer. | ||
| /// - Parameters: | ||
| /// - currentValue: A binding to the current value. | ||
| /// - model: A model that defines the appearance properties. | ||
| public init( | ||
| currentValue: Binding<CGFloat>, | ||
| model: ProgressBarVM = .init() | ||
| ) { | ||
| self._currentValue = currentValue | ||
| self.model = model | ||
| } | ||
|
|
||
| // MARK: - Body | ||
|
|
||
| public var body: some View { | ||
| GeometryReader { geometry in | ||
| switch self.model.style { | ||
| case .light: | ||
| HStack(spacing: 4) { | ||
| RoundedRectangle(cornerRadius: self.model.computedCornerRadius) | ||
| .foregroundStyle(self.model.barColor.color) | ||
| .frame(width: geometry.size.width * self.progress) | ||
| RoundedRectangle(cornerRadius: self.model.computedCornerRadius) | ||
| .foregroundStyle(self.model.backgroundColor.color) | ||
| .frame(width: geometry.size.width * (1 - self.progress)) | ||
| } | ||
|
|
||
| case .filled: | ||
| ZStack(alignment: .leading) { | ||
| RoundedRectangle(cornerRadius: self.model.computedCornerRadius) | ||
| .foregroundStyle(self.model.color.main.color) | ||
| .frame(width: geometry.size.width) | ||
|
|
||
| RoundedRectangle(cornerRadius: self.model.innerCornerRadius) | ||
| .foregroundStyle(self.model.color.contrast.color) | ||
| .frame(width: (geometry.size.width - self.model.innerBarPadding * 2) * self.progress) | ||
| .padding(.vertical, self.model.innerBarPadding) | ||
| .padding(.horizontal, self.model.innerBarPadding) | ||
| } | ||
|
|
||
| case .striped: | ||
| ZStack(alignment: .leading) { | ||
| RoundedRectangle(cornerRadius: self.model.computedCornerRadius) | ||
| .foregroundStyle(self.model.color.main.color) | ||
| .frame(width: geometry.size.width) | ||
|
|
||
| RoundedRectangle(cornerRadius: self.model.innerCornerRadius) | ||
| .foregroundStyle(self.model.color.contrast.color) | ||
| .frame(width: (geometry.size.width - self.model.innerBarPadding * 2) * self.progress) | ||
| .padding(.vertical, self.model.innerBarPadding) | ||
| .padding(.horizontal, self.model.innerBarPadding) | ||
|
|
||
| StripesShape(model: self.model) | ||
| .foregroundStyle(self.model.color.main.color) | ||
| .cornerRadius(self.model.computedCornerRadius) | ||
| .clipped() | ||
| } | ||
| } | ||
| } | ||
| .animation(.spring, value: self.progress) | ||
| .frame(height: self.model.barHeight) | ||
| .onAppear { | ||
| self.model.validateMinMaxValues() | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // MARK: - Helpers | ||
|
|
||
| struct StripesShape: Shape { | ||
| var model: ProgressBarVM | ||
|
|
||
| func path(in rect: CGRect) -> Path { | ||
| self.model.stripesPath(in: rect) | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.