├── .gitignore
├── Sources
├── progress-demo.gif
└── SwiftProgress
│ ├── CircularProgress.swift
│ └── LinearProgress.swift
├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ └── IDEWorkspaceChecks.plist
├── Package.swift
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 |
--------------------------------------------------------------------------------
/Sources/progress-demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EnesKaraosman/SwiftProgress/HEAD/Sources/progress-demo.gif
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.2
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: "SwiftProgress",
8 | platforms: [
9 | .iOS(.v13),
10 | .macOS(.v10_14),
11 | .tvOS(.v13),
12 | .watchOS(.v6)
13 | ],
14 | products: [
15 | // Products define the executables and libraries produced by a package, and make them visible to other packages.
16 | .library(
17 | name: "SwiftProgress",
18 | targets: ["SwiftProgress"]),
19 | ],
20 | dependencies: [
21 | // Dependencies declare other packages that this package depends on.
22 | // .package(url: /* package url */, from: "1.0.0"),
23 | ],
24 | targets: [
25 | // Targets are the basic building blocks of a package. A target can define a module or a test suite.
26 | // Targets can depend on other targets in this package, and on products in packages which this package depends on.
27 | .target(
28 | name: "SwiftProgress",
29 | dependencies: [])
30 | ]
31 | )
32 |
--------------------------------------------------------------------------------
/Sources/SwiftProgress/CircularProgress.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CircularProgress.swift
3 | //
4 | //
5 | // Created by Enes Karaosman on 13.04.2020.
6 | //
7 |
8 | import SwiftUI
9 |
10 | public struct CircularProgress: Animatable, View {
11 |
12 | /// Between 0 - 100
13 | private var progress: CGFloat
14 | private let lineWidth: CGFloat
15 | private var foregroundColor: Color?
16 | private let backgroundColor: Color
17 | private var gradient: LinearGradient?
18 |
19 | public var animatableData: Double {
20 | get {
21 | return Double(progress)
22 | }
23 | set {
24 | progress = CGFloat(newValue)
25 | }
26 | }
27 |
28 | private var overlay: AnyView {
29 | if self.foregroundColor != nil {
30 | return AnyView(
31 | RingShape(progress: progress, lineWidth: lineWidth)
32 | .foregroundColor(self.foregroundColor)
33 | )
34 | } else {
35 | return AnyView(
36 | self.gradient!.clipShape(RingShape(progress: progress, lineWidth: lineWidth))
37 | )
38 | }
39 | }
40 |
41 | public init(progress: CGFloat, lineWidth: CGFloat, foregroundColor: Color, backgroundColor: Color = .clear) {
42 | self.progress = progress
43 | self.lineWidth = lineWidth
44 | self.foregroundColor = foregroundColor
45 | self.backgroundColor = backgroundColor
46 | }
47 |
48 | public init(progress: CGFloat, lineWidth: CGFloat, gradient: LinearGradient, backgroundColor: Color = .clear) {
49 | self.progress = progress
50 | self.lineWidth = lineWidth
51 | self.gradient = gradient
52 | self.backgroundColor = backgroundColor
53 | }
54 |
55 | public var body: some View {
56 | ZStack {
57 | RingShape(progress: 100, lineWidth: lineWidth)
58 | .foregroundColor(backgroundColor)
59 |
60 | RingShape(progress: progress, lineWidth: lineWidth)
61 | .overlay(overlay)
62 | }
63 | }
64 |
65 | }
66 |
67 | public struct RingShape: Shape {
68 |
69 | /// Between 0 - 100
70 | public let progress: CGFloat
71 | public let lineWidth: CGFloat
72 |
73 | public func path(in rect: CGRect) -> Path {
74 |
75 | let center = CGPoint(x: rect.midX, y: rect.midY)
76 | let radius = rect.midX - lineWidth
77 |
78 | var path = Path()
79 |
80 | path.addArc(
81 | center: center,
82 | radius: radius,
83 | startAngle: .degrees(0),
84 | endAngle: .degrees(3.6 * Double(progress)),
85 | clockwise: false
86 | )
87 |
88 | return path.strokedPath(.init(lineWidth: lineWidth, lineCap: .round, lineJoin: .round))
89 |
90 | }
91 |
92 | }
93 |
--------------------------------------------------------------------------------
/Sources/SwiftProgress/LinearProgress.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LinearProgress.swift
3 | //
4 | //
5 | // Created by Enes Karaosman on 13.04.2020.
6 | //
7 |
8 | import SwiftUI
9 |
10 | public struct LinearProgress: Animatable, View {
11 |
12 | public enum FillAxis {
13 | case horizontal
14 | case vertical
15 | }
16 |
17 | /// Between 0 - 100
18 | private var progress: CGFloat
19 |
20 | private let cornerRadius: CGFloat
21 | private let backgroundColor: Color
22 | private var foregroundColor: Color?
23 | private var gradient: LinearGradient?
24 | private let fillAxis: FillAxis
25 |
26 | public var animatableData: Double {
27 | get {
28 | return Double(progress)
29 | }
30 | set {
31 | progress = CGFloat(newValue)
32 | }
33 | }
34 |
35 | private var overlay: AnyView {
36 | if self.foregroundColor != nil {
37 | return AnyView( Rectangle().foregroundColor(self.foregroundColor) )
38 | } else {
39 | return AnyView( self.gradient! )
40 | }
41 | }
42 |
43 | public init(progress: CGFloat, foregroundColor: Color, backgroundColor: Color = .clear, cornerRadius: CGFloat = 8, fillAxis: FillAxis = .horizontal) {
44 | self.progress = progress
45 | self.backgroundColor = backgroundColor
46 | self.foregroundColor = foregroundColor
47 | self.cornerRadius = cornerRadius
48 | self.fillAxis = fillAxis
49 | }
50 |
51 | public init(progress: CGFloat, gradient: LinearGradient, backgroundColor: Color = .clear, cornerRadius: CGFloat = 8, fillAxis: FillAxis = .horizontal) {
52 | self.progress = progress
53 | self.backgroundColor = backgroundColor
54 | self.gradient = gradient
55 | self.cornerRadius = cornerRadius
56 | self.fillAxis = fillAxis
57 | }
58 |
59 | private func needsToBeFilledArea(totalArea: CGFloat) -> CGFloat {
60 | return totalArea * (100 - self.progress) / 100
61 | }
62 |
63 | private func calculateOffset(totalArea: CGSize) -> CGSize {
64 | if self.fillAxis == .horizontal {
65 | return CGSize(
66 | width: -self.needsToBeFilledArea(totalArea: totalArea.width),
67 | height: 0
68 | )
69 | }
70 | return CGSize(
71 | width: 0,
72 | height: self.needsToBeFilledArea(totalArea: totalArea.height)
73 | )
74 | }
75 |
76 | public var body: some View {
77 |
78 | GeometryReader { geometry in
79 |
80 | Rectangle().foregroundColor(self.backgroundColor)
81 | .overlay(
82 | self.overlay
83 | .offset(self.calculateOffset(totalArea: geometry.size))
84 | )
85 | .clipShape(Rectangle())
86 | .cornerRadius(self.cornerRadius)
87 |
88 | }
89 |
90 | }
91 |
92 | }
93 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SwiftProgress
2 |
3 | LinearProgress bar & CircularProgress bar for SwiftUI.
4 |
5 | **Demo GIF**
6 |
7 | 
8 |
9 | ### Installation
10 |
11 | #### Swift Package Manager
12 |
13 | Add via https://github.com/EnesKaraosman/SwiftProgress.git
14 |
15 | ### Usage
16 |
17 | #### LinearProgress
18 |
19 | ```swift
20 | // Available constructors
21 |
22 | LinearProgress(
23 | progress: CGFloat,
24 | foregroundColor: Color,
25 | backgroundColor: Color = .clear,
26 | cornerRadius: CGFloat = 8
27 | )
28 |
29 | LinearProgress(
30 | progress: CGFloat,
31 | gradient: LinearGradient,
32 | backgroundColor: Color = .clear,
33 | cornerRadius: CGFloat = 8
34 | )
35 | ```
36 |
37 | #### CircularProgress
38 |
39 | ```swift
40 | // Available constructors
41 |
42 | CircularProgress(
43 | progress: CGFloat,
44 | lineWidth: CGFloat,
45 | foregroundColor: Color,
46 | backgroundColor: Color = .clear,
47 | fillAxis: FillAxis = .horizontal // vertical & horizontal options
48 | )
49 |
50 | CircularProgress(
51 | progress: CGFloat,
52 | lineWidth: CGFloat,
53 | gradient: LinearGradient,
54 | backgroundColor: Color = .clear,
55 | fillAxis: FillAxis = .horizontal // vertical & horizontal options
56 | )
57 | ```
58 |
59 | #### Full Example (reflects the demo GIF)
60 |
61 | ```swift
62 | @State private var fillPercentage: CGFloat = 20
63 |
64 | var body: some View {
65 |
66 |
67 | VStack {
68 |
69 | LinearProgress(
70 | progress: self.fillPercentage,
71 | foregroundColor: .green,
72 | backgroundColor: Color.green.opacity(0.2),
73 | fillAxis: .vertical
74 | )
75 | .frame(width: 40, height: 100)
76 |
77 | LinearProgress(
78 | progress: self.fillPercentage,
79 | gradient: LinearGradient(
80 | gradient: .init(colors: [.yellow, .red]),
81 | startPoint: .leading,
82 | endPoint: .trailing
83 | ),
84 | backgroundColor: Color.blue.opacity(0.2),
85 | cornerRadius: 16
86 | )
87 | .frame(height: 50)
88 | .padding()
89 |
90 | LinearProgress(progress: self.fillPercentage, foregroundColor: Color.green.opacity(0.3))
91 | .clipShape(Capsule())
92 | .frame(height: 50)
93 | .padding()
94 |
95 | ZStack {
96 | LinearProgress(progress: self.fillPercentage, foregroundColor: .blue, cornerRadius: 0)
97 | .frame(height: 50)
98 | .padding()
99 |
100 | Text(String(format: "%1.f", self.fillPercentage))
101 | .font(.title)
102 | .foregroundColor(.pink)
103 | .shadow(radius: 4)
104 | }
105 |
106 |
107 | Slider(value: self.$fillPercentage, in: 0...100)
108 | .padding()
109 |
110 | HStack {
111 | CircularProgress(currentPercentage: self.fillPercentage, lineWidth: 8, foregroundColor: .orange)
112 | .rotationEffect(.degrees(-90))
113 |
114 | ZStack {
115 | CircularProgress(
116 | currentPercentage: self.fillPercentage,
117 | lineWidth: 16,
118 | gradient: LinearGradient(
119 | gradient: .init(colors: [.yellow, .red]),
120 | startPoint: .leading,
121 | endPoint: .trailing
122 | ),
123 | backgroundColor: .gray
124 | )
125 |
126 | Text(String(format: "%1.f", self.fillPercentage))
127 | .font(.title)
128 | .foregroundColor(.orange)
129 | .shadow(radius: 8)
130 | }
131 | }.frame(height: 200)
132 |
133 | }
134 | }
135 | ```
136 |
--------------------------------------------------------------------------------