├── .github
└── FUNDING.yml
├── .gitignore
├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ └── IDEWorkspaceChecks.plist
├── Gifs
└── example.gif
├── Images
├── ColorOne.png
├── ColorTwo.png
├── Fill.png
├── FontOne.png
├── FontTwo.png
├── LineCap.png
├── LineWidth.png
├── ShowBottomText.png
└── ShowText.png
├── LICENSE
├── Package.swift
├── README.md
├── Sources
└── CircularProgress
│ └── CircularProgress.swift
└── Tests
├── CircularProgressTests
├── CircularProgressTests.swift
└── XCTestManifests.swift
└── LinuxMain.swift
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: arnavmotwani
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Gifs/example.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArnavMotwani/CircularProgressSwiftUI/017efe091152bad991a3a4c935a444facb0ed3d3/Gifs/example.gif
--------------------------------------------------------------------------------
/Images/ColorOne.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArnavMotwani/CircularProgressSwiftUI/017efe091152bad991a3a4c935a444facb0ed3d3/Images/ColorOne.png
--------------------------------------------------------------------------------
/Images/ColorTwo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArnavMotwani/CircularProgressSwiftUI/017efe091152bad991a3a4c935a444facb0ed3d3/Images/ColorTwo.png
--------------------------------------------------------------------------------
/Images/Fill.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArnavMotwani/CircularProgressSwiftUI/017efe091152bad991a3a4c935a444facb0ed3d3/Images/Fill.png
--------------------------------------------------------------------------------
/Images/FontOne.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArnavMotwani/CircularProgressSwiftUI/017efe091152bad991a3a4c935a444facb0ed3d3/Images/FontOne.png
--------------------------------------------------------------------------------
/Images/FontTwo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArnavMotwani/CircularProgressSwiftUI/017efe091152bad991a3a4c935a444facb0ed3d3/Images/FontTwo.png
--------------------------------------------------------------------------------
/Images/LineCap.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArnavMotwani/CircularProgressSwiftUI/017efe091152bad991a3a4c935a444facb0ed3d3/Images/LineCap.png
--------------------------------------------------------------------------------
/Images/LineWidth.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArnavMotwani/CircularProgressSwiftUI/017efe091152bad991a3a4c935a444facb0ed3d3/Images/LineWidth.png
--------------------------------------------------------------------------------
/Images/ShowBottomText.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArnavMotwani/CircularProgressSwiftUI/017efe091152bad991a3a4c935a444facb0ed3d3/Images/ShowBottomText.png
--------------------------------------------------------------------------------
/Images/ShowText.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ArnavMotwani/CircularProgressSwiftUI/017efe091152bad991a3a4c935a444facb0ed3d3/Images/ShowText.png
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Arnav Motwani
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.5
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "CircularProgress",
8 | platforms: [
9 | .iOS(.v15),
10 | .macOS(.v12)
11 | ],
12 | products: [
13 | // Products define the executables and libraries a package produces, and make them visible to other packages.
14 | .library(
15 | name: "CircularProgress",
16 | targets: ["CircularProgress"]),
17 | ],
18 | dependencies: [
19 | // Dependencies declare other packages that this package depends on.
20 | // .package(url: /* package url */, from: "1.0.0"),
21 | ],
22 | targets: [
23 | // Targets are the basic building blocks of a package. A target can define a module or a test suite.
24 | // Targets can depend on other targets in this package, and on products in packages this package depends on.
25 | .target(
26 | name: "CircularProgress",
27 | dependencies: []),
28 | .testTarget(
29 | name: "CircularProgressTests",
30 | dependencies: ["CircularProgress"]),
31 | ]
32 | )
33 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # CircularProgress
2 |
3 | SwiftUI package that creates an animated circular progress bar
4 |
5 | ### Installation: It requires at least iOS 15, iPadOS 15, macOS 12 and Xcode 13!
6 |
7 | In Xcode go to `File -> Add Packages...` and paste in the repo's url: `https://github.com/ArnavMotwani/CircularProgressSwiftUI.git` then either select a version or the main branch
8 |
9 | I will update the main branch more frequently with minor changes, while the version will only increase with significant changes.
10 |
11 | There also a branch called iOS13 which supports iOS 13+ and macOS 10_15+
12 |
13 | ## Usage:
14 |
15 | Import the package into the file with `import CircularProgress`
16 |
17 | ### Example:
18 | Here is how the default view, with no customizations, can be implemented
19 |
20 |
21 |
22 |
23 |
24 | ```swift
25 | import SwiftUI
26 | import CircularProgress
27 |
28 | struct ContentView: View {
29 | @State var count = 0
30 | let total = 10
31 | var progress: CGFloat{
32 | return CGFloat(count)/CGFloat(total)
33 | }
34 | var body: some View {
35 | VStack {
36 | CircularProgressView(count: count, total: total, progress: progress)
37 | .padding(50)
38 | HStack{
39 | Button("Decrease", action: {self.count -= 1})
40 | Spacer()
41 | Button("Increase", action: {self.count += 1})
42 | }
43 | .padding(50)
44 | }
45 | }
46 | }
47 |
48 | ```
49 | ## Fill Customization:
50 | The Progress Bar can be filled with a Linear or an Angular Gradient. By default the fill is `LinearGradient(gradient: Gradient(colors: [Color.green, Color.blue]), startPoint: .top, endPoint: .bottom)` however you can pass a custom Linear or Angular Gradient to the fill Parameter.
51 |
52 | ## Parameters:
53 |
54 | | parameter | optional? | type | description | default |
55 | |----------------|-----------|-----------------------------------|-------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------|
56 | | count | required | Int | Current value (larger text in the centre) | - |
57 | | total | required | Int | Total value (smaller text in the centre) | - |
58 | | progress | required | CGFloat | Progress of the bar | - |
59 | | fontOne | optional | Font | Font of larger text in the centre | Font.system(size: 75, weight: .bold, design: .rounded) |
60 | | fontTwo | optional | Font | Font of smaller text in the centre | Font.system(size: 25, weight: .bold, design: .rounded) |
61 | | colorOne | optional | Color | Color of larger text in the centre | Color.primary |
62 | | colorTwo | optional | Color | Color of smaller text in the centre | Color.gray |
63 | | fill | optional | LinearGradient or AngularGradient | Fill of the progress bar | LinearGradient(gradient: Gradient(colors: [Color.green, Color.blue]), startPoint: .top, endPoint: .bottom) |
64 | | lineWidth | optional | CGFloat | Width of the progress bar | 25.0 |
65 | | lineCap | optional | CGLineCap | The line cap at the end of the progress bar | CGLineCap.round |
66 | | showText | optional | Bool | Choose whether the text at the centre is displayed or not | true |
67 | | showBottomText | optional | Bool | Choose whether the bottom text in the centre is visible (if showText is true) | true |
68 |
69 | ### Examples
70 |
71 | #### fontOne
72 |
73 |
74 |
75 |
76 | ```swift
77 | CircularProgressView(count: count, total: total, progress: progress, fontOne: Font.title.bold())
78 | ```
79 | ---
80 | #### fontTwo
81 |
82 |
83 |
84 |
85 | ```swift
86 | CircularProgressView(count: count, total: total, progress: progress, fontTwo: Font.title2)
87 | ```
88 | ---
89 | #### colorOne
90 |
91 |
92 |
93 |
94 | ```swift
95 | CircularProgressView(count: count, total: total, progress: progress, colorOne: Color.blue)
96 | ```
97 | ---
98 | #### colorTwo
99 |
100 |
101 |
102 |
103 | ```swift
104 | CircularProgressView(count: count, total: total, progress: progress, colorTwo: Color.blue)
105 | ```
106 | ---
107 | #### fill
108 |
109 |
110 |
111 |
112 | ```swift
113 | CircularProgressView(count: count, total: total, progress: progress, fill: LinearGradient(gradient: Gradient(colors: [Color.red, Color.blue]), startPoint: .leading, endPoint: .trailing))
114 | ```
115 | ---
116 | #### lineWidth
117 |
118 |
119 |
120 |
121 | ```swift
122 | CircularProgressView(count: count, total: total, progress: progress, lineWidth: 50)
123 | ```
124 | ---
125 | #### lineCap
126 |
127 |
128 |
129 |
130 | ```swift
131 | CircularProgressView(count: count, total: total, progress: progress, lineCap: CGLineCap.square)
132 | ```
133 | ---
134 | #### showText
135 |
136 |
137 |
138 |
139 | ```swift
140 | CircularProgressView(count: count, total: total, progress: progress, showText: false)
141 | ```
142 | ---
143 | #### showBottomText
144 |
145 |
146 |
147 |
148 | ```swift
149 | CircularProgressView(count: count, total: total, progress: progress, showBottomText: false)
150 | }
151 | ```
152 |
153 |
154 |
--------------------------------------------------------------------------------
/Sources/CircularProgress/CircularProgress.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | public struct CircularProgressView: View {
4 |
5 | //MARK: Required variables
6 | var count: Int
7 | var total: Int
8 | var progress: CGFloat
9 |
10 | //MARK: Optional variables
11 | //fontOne for the current value text and fontTwo for the total value text in the centre.
12 | var fontOne: Font
13 | var fontTwo: Font
14 |
15 | //colorOne for the current value text and colorTwo for the total value text in the centre.
16 | var colorOne: Color
17 | var colorTwo: Color
18 |
19 | //The fill variable is used to choose the gradient inside the progress bar
20 | var fill: AnyShapeStyle
21 | //The lineWidth variable is used to choose the width of the progress bar (Not the enter view)
22 | var lineWidth: CGFloat
23 | //The lineCap variable is used to choose the line caps at the end of the progress bar
24 | var lineCap: CGLineCap
25 | //Choose whether the text in the centre is shown.
26 | var showText: Bool
27 | //Choose whether the bottom text in the centre of the progress bar is shown.
28 | var showBottomText: Bool
29 |
30 | //MARK: Init
31 | //Declared to allow view access the package
32 | //Also sets defaults for optional variables
33 | public init(count: Int,
34 | total: Int,
35 | progress: CGFloat,
36 | fontOne: Font = Font.system(size: 75, weight: .bold, design: .rounded),
37 | fontTwo: Font = Font.system(size: 25, weight: .bold, design: .rounded),
38 | colorOne: Color = Color.primary,
39 | colorTwo: Color = Color.gray,
40 | fill: LinearGradient = LinearGradient(gradient: Gradient(colors: [Color.green, Color.blue]), startPoint: .top, endPoint: .bottom),
41 | lineWidth: CGFloat = 25.0,
42 | lineCap: CGLineCap = CGLineCap.round,
43 | showText: Bool = true,
44 | showBottomText: Bool = true) {
45 |
46 | self.count = count
47 | self.total = total
48 | self.progress = progress
49 | self.fontOne = fontOne
50 | self.fontTwo = fontTwo
51 | self.colorOne = colorOne
52 | self.colorTwo = colorTwo
53 | self.fill = AnyShapeStyle(fill)
54 | self.lineWidth = lineWidth
55 | self.lineCap = lineCap
56 | self.showText = showText
57 | self.showBottomText = showBottomText
58 | }
59 |
60 | public init(count: Int,
61 | total: Int,
62 | progress: CGFloat,
63 | fontOne: Font = Font.system(size: 75, weight: .bold, design: .rounded),
64 | fontTwo: Font = Font.system(size: 25, weight: .bold, design: .rounded),
65 | colorOne: Color = Color.primary,
66 | colorTwo: Color = Color.gray,
67 | fill: AngularGradient,
68 | lineWidth: CGFloat = 25.0,
69 | lineCap: CGLineCap = CGLineCap.round,
70 | showText: Bool = true,
71 | showBottomText: Bool = true) {
72 |
73 | self.count = count
74 | self.total = total
75 | self.progress = progress
76 | self.fontOne = fontOne
77 | self.fontTwo = fontTwo
78 | self.colorOne = colorOne
79 | self.colorTwo = colorTwo
80 | self.fill = AnyShapeStyle(fill)
81 | self.lineWidth = lineWidth
82 | self.lineCap = lineCap
83 | self.showText = showText
84 | self.showBottomText = showBottomText
85 | }
86 |
87 | //MARK: View
88 | public var body: some View {
89 | ZStack{
90 | //Background line for progress
91 | Circle()
92 | .stroke(lineWidth: lineWidth)
93 | .opacity(0.3)
94 | .foregroundColor(Color.secondary)
95 |
96 | //Trimmed circle to represent progress
97 | Circle()
98 | .trim(from: 0.0, to: CGFloat(min(self.progress, 1.0)))
99 | .stroke(fill ,style: StrokeStyle(lineWidth: lineWidth, lineCap: lineCap, lineJoin: .round))
100 | .rotationEffect(Angle(degrees: 270.0))
101 | .animation(.linear, value: progress)
102 |
103 | if showText {
104 | //Text at the centre
105 | VStack {
106 | //Text for current value
107 | Text("\(count)")
108 | .font(fontOne)
109 | .foregroundColor(colorOne)
110 | if showBottomText{
111 | //Text for total value
112 | Text("/ \(total)")
113 | .font(fontTwo)
114 | .foregroundColor(colorTwo)
115 | }
116 | }
117 | }
118 | }
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/Tests/CircularProgressTests/CircularProgressTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | import SwiftUI
3 | @testable import CircularProgress
4 |
5 | final class CircularProgressTests: XCTestCase {
6 | func testLinear() {
7 | let item = CircularProgressView(count: 5, total: 10, progress: 0.5)
8 | XCTAssertEqual(item.progress, 0.5)
9 | }
10 | func testAngular() {
11 | let item = CircularProgressView(count: 5, total: 10, progress: 0.5, fill: AngularGradient(colors: [.blue], center: .center))
12 | XCTAssertEqual(item.progress, 0.5)
13 | }
14 |
15 | static var allTests = [
16 | ("testLinear", testLinear),
17 | ("testAngular", testAngular)
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------
/Tests/CircularProgressTests/XCTestManifests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | #if !canImport(ObjectiveC)
4 | public func allTests() -> [XCTestCaseEntry] {
5 | return [
6 | testCase(CircularProgressTests.allTests),
7 | ]
8 | }
9 | #endif
10 |
--------------------------------------------------------------------------------
/Tests/LinuxMain.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | import CircularProgressTests
4 |
5 | var tests = [XCTestCaseEntry]()
6 | tests += CircularProgressTests.allTests()
7 | XCTMain(tests)
8 |
--------------------------------------------------------------------------------