Skip to content

Commit 80b9d13

Browse files
Sticks Loading and Progress bar Animation
1 parent c292ca4 commit 80b9d13

File tree

7 files changed

+641
-0
lines changed

7 files changed

+641
-0
lines changed

SSSwiftUIAnimations.xcodeproj/project.pbxproj

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@
77
objects = {
88

99
/* Begin PBXBuildFile section */
10+
223F502E2C2E95F3006C68CE /* CircularLoading.swift in Sources */ = {isa = PBXBuildFile; fileRef = 223F502D2C2E95F3006C68CE /* CircularLoading.swift */; };
11+
223F50342C2E9636006C68CE /* LinearLoading.swift in Sources */ = {isa = PBXBuildFile; fileRef = 223F502F2C2E9636006C68CE /* LinearLoading.swift */; };
12+
223F50352C2E9636006C68CE /* LinearProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 223F50302C2E9636006C68CE /* LinearProgress.swift */; };
13+
223F50362C2E9636006C68CE /* CircularProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 223F50312C2E9636006C68CE /* CircularProgress.swift */; };
14+
223F50372C2E9636006C68CE /* CircularReverseProgreessBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 223F50322C2E9636006C68CE /* CircularReverseProgreessBar.swift */; };
15+
223F50382C2E9636006C68CE /* Stick.swift in Sources */ = {isa = PBXBuildFile; fileRef = 223F50332C2E9636006C68CE /* Stick.swift */; };
1016
2BC2D8F328CF3A6F00CAB302 /* SSSwiftUIAnimationsApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2BC2D8F228CF3A6F00CAB302 /* SSSwiftUIAnimationsApp.swift */; };
1117
2BC2D8F528CF3A6F00CAB302 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2BC2D8F428CF3A6F00CAB302 /* ContentView.swift */; };
1218
2BC2D8F728CF3A7000CAB302 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2BC2D8F628CF3A7000CAB302 /* Assets.xcassets */; };
@@ -35,6 +41,12 @@
3541
/* End PBXBuildFile section */
3642

3743
/* Begin PBXFileReference section */
44+
223F502D2C2E95F3006C68CE /* CircularLoading.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CircularLoading.swift; sourceTree = "<group>"; };
45+
223F502F2C2E9636006C68CE /* LinearLoading.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinearLoading.swift; sourceTree = "<group>"; };
46+
223F50302C2E9636006C68CE /* LinearProgress.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinearProgress.swift; sourceTree = "<group>"; };
47+
223F50312C2E9636006C68CE /* CircularProgress.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CircularProgress.swift; sourceTree = "<group>"; };
48+
223F50322C2E9636006C68CE /* CircularReverseProgreessBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CircularReverseProgreessBar.swift; sourceTree = "<group>"; };
49+
223F50332C2E9636006C68CE /* Stick.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Stick.swift; sourceTree = "<group>"; };
3850
2BC2D8EF28CF3A6F00CAB302 /* SSSwiftUIAnimations.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SSSwiftUIAnimations.app; sourceTree = BUILT_PRODUCTS_DIR; };
3951
2BC2D8F228CF3A6F00CAB302 /* SSSwiftUIAnimationsApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSSwiftUIAnimationsApp.swift; sourceTree = "<group>"; };
4052
2BC2D8F428CF3A6F00CAB302 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
@@ -74,6 +86,19 @@
7486
/* End PBXFrameworksBuildPhase section */
7587

7688
/* Begin PBXGroup section */
89+
223F502C2C2E95D6006C68CE /* SticksAnimations */ = {
90+
isa = PBXGroup;
91+
children = (
92+
223F502D2C2E95F3006C68CE /* CircularLoading.swift */,
93+
223F50312C2E9636006C68CE /* CircularProgress.swift */,
94+
223F50322C2E9636006C68CE /* CircularReverseProgreessBar.swift */,
95+
223F502F2C2E9636006C68CE /* LinearLoading.swift */,
96+
223F50302C2E9636006C68CE /* LinearProgress.swift */,
97+
223F50332C2E9636006C68CE /* Stick.swift */,
98+
);
99+
path = SticksAnimations;
100+
sourceTree = "<group>";
101+
};
77102
2BC2D8E628CF3A6F00CAB302 = {
78103
isa = PBXGroup;
79104
children = (
@@ -162,6 +187,7 @@
162187
B1DE99D72C060E1A006995FB /* Sources */ = {
163188
isa = PBXGroup;
164189
children = (
190+
223F502C2C2E95D6006C68CE /* SticksAnimations */,
165191
B14AB36A2BC40286004B09C4 /* ProgressAnimation */,
166192
469963A3290FCE1900DC01AD /* ArrowLeftRightAnimation */,
167193
);
@@ -265,6 +291,7 @@
265291
B177713F2BF39A60001723EC /* ModelClass.swift in Sources */,
266292
B10677FE2BE8D0D400957B4E /* DownArrow.swift in Sources */,
267293
B1098E7D2BD94ED900BC19DD /* WaveView.swift in Sources */,
294+
223F50372C2E9636006C68CE /* CircularReverseProgreessBar.swift in Sources */,
268295
B19E0B662BF7498700E65974 /* ExampleProgressView.swift in Sources */,
269296
B18792612AA5A0D2006F2CC9 /* CircularView.swift in Sources */,
270297
B153FD152BFB7A7900AEFE83 /* ExampleLRArrowView.swift in Sources */,
@@ -274,8 +301,13 @@
274301
469963A5290FCE3600DC01AD /* SSLRArrowView.swift in Sources */,
275302
B153FD0C2BFB3C1800AEFE83 /* LRArrowAnimStyle.swift in Sources */,
276303
B153FD132BFB71F500AEFE83 /* FilledStrokeCircle.swift in Sources */,
304+
223F50362C2E9636006C68CE /* CircularProgress.swift in Sources */,
305+
223F50352C2E9636006C68CE /* LinearProgress.swift in Sources */,
306+
223F502E2C2E95F3006C68CE /* CircularLoading.swift in Sources */,
277307
2BC2D8F328CF3A6F00CAB302 /* SSSwiftUIAnimationsApp.swift in Sources */,
278308
B1DFCA532BF4FC7900F01505 /* ArrowView.swift in Sources */,
309+
223F50382C2E9636006C68CE /* Stick.swift in Sources */,
310+
223F50342C2E9636006C68CE /* LinearLoading.swift in Sources */,
279311
B11B983A2BCE9C3F00D76016 /* CheckView.swift in Sources */,
280312
B1DFCA512BF4FA3D00F01505 /* ProgressCircle.swift in Sources */,
281313
B14AB36C2BC41B05004B09C4 /* ProgressView.swift in Sources */,
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
//
2+
// CircularLoading.swift
3+
// SSSwiftUIAnimations
4+
//
5+
// Created by Brijesh Barasiya on 29/01/24.
6+
//
7+
8+
import SwiftUI
9+
10+
struct CircularLoading: View {
11+
@State private var sticks: [Stick]
12+
private let circleSize: CGFloat
13+
private let stickWidth: CGFloat
14+
private let filledColor: Color
15+
private let unFilledColor: Color
16+
private let perStickDuration: Double
17+
18+
init(
19+
size: CGSize,
20+
filledColor: Color,
21+
unFilledColor: Color,
22+
duration: Double
23+
) {
24+
let adjustedSize = min(size.width, size.height)
25+
let adjustedStickWidth = adjustedSize * 0.05
26+
let totalStickCount: Int = Int(adjustedSize / (adjustedStickWidth * 0.75))
27+
self.sticks = Array(
28+
repeating: Stick(xAxis: 0, stickHeight: (adjustedSize * 0.20), color: unFilledColor),
29+
count: totalStickCount
30+
)
31+
self.circleSize = CGFloat(adjustedSize)
32+
self.stickWidth = CGFloat(adjustedStickWidth)
33+
self.filledColor = filledColor
34+
self.unFilledColor = unFilledColor
35+
self.perStickDuration = duration / Double(totalStickCount)
36+
}
37+
38+
var body: some View {
39+
Circle()
40+
.frame(width: circleSize)
41+
.foregroundColor(Color.clear)
42+
.overlay {
43+
ForEach(0..<sticks.count, id: \.self) { index in
44+
Rectangle()
45+
.frame(width: stickWidth, height: sticks[index].stickHeight)
46+
.foregroundColor(sticks[index].color)
47+
.offset(y: (circleSize - sticks[index].stickHeight) / 2)
48+
.rotationEffect(
49+
.degrees(Double((CGFloat(index) + sticks[index].xAxis) * 360) / Double(sticks.count))
50+
)
51+
}
52+
}
53+
.onAppear {
54+
animateStickView(index: 0, color: filledColor)
55+
}
56+
}
57+
58+
private func animateStickView(index: Int, color: Color) {
59+
if #available(iOS 17.0, *) {
60+
withAnimation(Animation.linear(duration: perStickDuration)) {
61+
updateStickViewProperties(index: index, color: color)
62+
} completion: {
63+
resertStickViewAnimation(index: index, color: color)
64+
}
65+
} else {
66+
withAnimation(Animation.linear(duration: perStickDuration)) {
67+
updateStickViewProperties(index: index, color: color)
68+
}
69+
DispatchQueue.main.asyncAfter(deadline: .now() + perStickDuration) {
70+
resertStickViewAnimation(index: index, color: color)
71+
}
72+
}
73+
}
74+
75+
private func updateStickViewProperties(index: Int, color: Color) {
76+
sticks[index].xAxis = 0.6
77+
sticks[index].color = color
78+
}
79+
80+
private func resertStickViewAnimation(index: Int, color: Color) {
81+
withAnimation(Animation.linear(duration: perStickDuration * 10)) {
82+
sticks[index].xAxis = 0
83+
}
84+
if (index == sticks.indices.last) {
85+
let newColor = switch color {
86+
case unFilledColor: filledColor
87+
case filledColor : unFilledColor
88+
default : filledColor
89+
}
90+
animateStickView(index: 0, color: newColor)
91+
} else {
92+
animateStickView(index: index + 1, color: color)
93+
}
94+
}
95+
}
96+
97+
#Preview {
98+
CircularLoading(
99+
size: CGSize(width: 150, height: 150),
100+
filledColor: .black,
101+
unFilledColor: .gray,
102+
duration: 1
103+
)
104+
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
//
2+
// CircularLoading.swift
3+
// SSSwiftUIAnimations
4+
//
5+
// Created by Brijesh Barasiya on 29/01/24.
6+
//
7+
8+
import SwiftUI
9+
10+
struct CircularProgress: View {
11+
@Binding private var percentage: Float
12+
@State private var sticks: [Stick]
13+
private let circleSize: CGFloat
14+
private let stickWidth: CGFloat
15+
private let filledColor: Color
16+
private let unFilledColor: Color
17+
private let perStickDuration: Double
18+
19+
init(
20+
percentage: Binding<Float>,
21+
size: CGSize,
22+
filledColor: Color,
23+
unFilledColor: Color,
24+
duration: Double
25+
) {
26+
let adjustedSize = min(size.width, size.height)
27+
let adjustedStickWidth = adjustedSize * 0.05
28+
let totalStickCount: Int = Int(adjustedSize / (adjustedStickWidth * 0.75))
29+
self._percentage = percentage
30+
self.sticks = Array(
31+
repeating: Stick(xAxis: 0, stickHeight: (adjustedSize * 0.20), color: unFilledColor),
32+
count: totalStickCount
33+
)
34+
self.circleSize = CGFloat(adjustedSize)
35+
self.stickWidth = CGFloat(adjustedStickWidth)
36+
self.filledColor = filledColor
37+
self.unFilledColor = unFilledColor
38+
self.perStickDuration = duration / Double(totalStickCount)
39+
40+
}
41+
42+
var body: some View {
43+
Circle()
44+
.frame(width: circleSize)
45+
.foregroundColor(Color.clear)
46+
.overlay {
47+
ForEach(0..<sticks.count, id: \.self) { index in
48+
Rectangle()
49+
.frame(width: stickWidth, height: sticks[index].stickHeight)
50+
.foregroundColor(sticks[index].color)
51+
.offset(y: (circleSize - sticks[index].stickHeight) / 2)
52+
.rotationEffect(
53+
.degrees(Double((CGFloat(index) + sticks[index].xAxis) * 360) / Double(sticks.count))
54+
)
55+
}
56+
}
57+
.onAppear {
58+
animateStickView(index: 0, color: filledColor)
59+
}
60+
}
61+
62+
private func animateStickView(index: Int, color: Color) {
63+
if #available(iOS 17.0, *) {
64+
withAnimation(Animation.linear(duration: perStickDuration)) {
65+
updateStickViewProperties(index: index, color: color)
66+
} completion: {
67+
resertStickViewAnimation(index: index, color: color)
68+
}
69+
} else {
70+
withAnimation(Animation.linear(duration: perStickDuration)) {
71+
updateStickViewProperties(index: index, color: color)
72+
}
73+
DispatchQueue.main.asyncAfter(deadline: .now() + perStickDuration) {
74+
resertStickViewAnimation(index: index, color: color)
75+
}
76+
}
77+
}
78+
79+
private func updateStickViewProperties(index: Int, color: Color) {
80+
changeStictsColor(color: unFilledColor)
81+
let validatedPercentage = min(max(0, percentage), 100)
82+
let sticksAccordingToPercentage = Double(sticks.count) * Double(validatedPercentage / 200)
83+
let numberOfSticksToChange = max(Int(sticksAccordingToPercentage), 0)
84+
for stickIndex in 0..<Int(numberOfSticksToChange) {
85+
updateStickColor(at: index + stickIndex, color: filledColor)
86+
updateStickColor(at: (index - 1) - stickIndex, color: filledColor)
87+
}
88+
if Double(numberOfSticksToChange) != round(sticksAccordingToPercentage) {
89+
updateStickColor(at: index + (Int(numberOfSticksToChange)), color: filledColor)
90+
}
91+
}
92+
93+
private func changeStictsColor(color: Color) {
94+
sticks.indices.forEach { updateStickColor(at: $0, color: color) }
95+
}
96+
97+
private func updateStickColor(at index: Int, color: Color) {
98+
let adjustedIndex = (index + sticks.count) % sticks.count
99+
sticks[adjustedIndex].color = color
100+
}
101+
102+
private func resertStickViewAnimation(index: Int, color: Color) {
103+
let nextIndex = (index == sticks.indices.last) ? 0 : index + 1
104+
animateStickView(index: nextIndex, color: color)
105+
}
106+
}
107+
108+
#Preview {
109+
CircularProgress(
110+
percentage: .constant(75),
111+
size: CGSize(width: 50, height: 250),
112+
filledColor: .green,
113+
unFilledColor: .gray,
114+
duration: 1
115+
)
116+
}

0 commit comments

Comments
 (0)