Skip to content

Commit 435f544

Browse files
Merge pull request #7 from SimformSolutionsPvtLtd/feature/UNT-T11575-left-right-arrow-animation
UNT-T11575 Left right arrow animation
2 parents f0bb0be + b86dfc6 commit 435f544

File tree

13 files changed

+471
-19
lines changed

13 files changed

+471
-19
lines changed

SSSwiftUIAnimations/SSSwiftUIAnimations.xcodeproj/project.pbxproj

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,20 @@
1111
2BC2D8F528CF3A6F00CAB302 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2BC2D8F428CF3A6F00CAB302 /* ContentView.swift */; };
1212
2BC2D8F728CF3A7000CAB302 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2BC2D8F628CF3A7000CAB302 /* Assets.xcassets */; };
1313
2BC2D8FA28CF3A7000CAB302 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2BC2D8F928CF3A7000CAB302 /* Preview Assets.xcassets */; };
14+
4636F36E291E1BD600C8DB5B /* LeftArrow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4636F36D291E1BD600C8DB5B /* LeftArrow.swift */; };
15+
469963A5290FCE3600DC01AD /* SSLRArrowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 469963A4290FCE3600DC01AD /* SSLRArrowView.swift */; };
1416
B10677FE2BE8D0D400957B4E /* DownArrow.swift in Sources */ = {isa = PBXBuildFile; fileRef = B10677FD2BE8D0D400957B4E /* DownArrow.swift */; };
1517
B1098E7D2BD94ED900BC19DD /* WaveView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1098E7C2BD94ED900BC19DD /* WaveView.swift */; };
1618
B11B983A2BCE9C3F00D76016 /* CheckView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B11B98392BCE9C3F00D76016 /* CheckView.swift */; };
1719
B14AB36C2BC41B05004B09C4 /* ProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B14AB36B2BC41B05004B09C4 /* ProgressView.swift */; };
20+
B153FD0C2BFB3C1800AEFE83 /* LRArrowAnimStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = B153FD0B2BFB3C1800AEFE83 /* LRArrowAnimStyle.swift */; };
1821
B153FD0E2BFB566000AEFE83 /* ExampleListRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = B153FD0D2BFB566000AEFE83 /* ExampleListRow.swift */; };
1922
B153FD102BFB649300AEFE83 /* ExampleListModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B153FD0F2BFB649300AEFE83 /* ExampleListModel.swift */; };
23+
B153FD132BFB71F500AEFE83 /* FilledStrokeCircle.swift in Sources */ = {isa = PBXBuildFile; fileRef = B153FD122BFB71F500AEFE83 /* FilledStrokeCircle.swift */; };
24+
B153FD152BFB7A7900AEFE83 /* ExampleLRArrowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B153FD142BFB7A7900AEFE83 /* ExampleLRArrowView.swift */; };
25+
B15FD7992C04785700752CEA /* CustomToolBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = B15FD7982C04785700752CEA /* CustomToolBar.swift */; };
2026
B177713F2BF39A60001723EC /* ModelClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = B177713E2BF39A60001723EC /* ModelClass.swift */; };
27+
B18792612AA5A0D2006F2CC9 /* CircularView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B18792602AA5A0D2006F2CC9 /* CircularView.swift */; };
2128
B19E0B662BF7498700E65974 /* ExampleProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B19E0B652BF7498700E65974 /* ExampleProgressView.swift */; };
2229
B1DFCA512BF4FA3D00F01505 /* ProgressCircle.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1DFCA502BF4FA3D00F01505 /* ProgressCircle.swift */; };
2330
B1DFCA532BF4FC7900F01505 /* ArrowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1DFCA522BF4FC7900F01505 /* ArrowView.swift */; };
@@ -30,13 +37,20 @@
3037
2BC2D8F428CF3A6F00CAB302 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
3138
2BC2D8F628CF3A7000CAB302 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
3239
2BC2D8F928CF3A7000CAB302 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
40+
4636F36D291E1BD600C8DB5B /* LeftArrow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LeftArrow.swift; sourceTree = "<group>"; };
41+
469963A4290FCE3600DC01AD /* SSLRArrowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSLRArrowView.swift; sourceTree = "<group>"; };
3342
B10677FD2BE8D0D400957B4E /* DownArrow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownArrow.swift; sourceTree = "<group>"; };
3443
B1098E7C2BD94ED900BC19DD /* WaveView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaveView.swift; sourceTree = "<group>"; };
3544
B11B98392BCE9C3F00D76016 /* CheckView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckView.swift; sourceTree = "<group>"; };
3645
B14AB36B2BC41B05004B09C4 /* ProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressView.swift; sourceTree = "<group>"; };
46+
B153FD0B2BFB3C1800AEFE83 /* LRArrowAnimStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LRArrowAnimStyle.swift; sourceTree = "<group>"; };
3747
B153FD0D2BFB566000AEFE83 /* ExampleListRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleListRow.swift; sourceTree = "<group>"; };
3848
B153FD0F2BFB649300AEFE83 /* ExampleListModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleListModel.swift; sourceTree = "<group>"; };
49+
B153FD122BFB71F500AEFE83 /* FilledStrokeCircle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilledStrokeCircle.swift; sourceTree = "<group>"; };
50+
B153FD142BFB7A7900AEFE83 /* ExampleLRArrowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleLRArrowView.swift; sourceTree = "<group>"; };
51+
B15FD7982C04785700752CEA /* CustomToolBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomToolBar.swift; sourceTree = "<group>"; };
3952
B177713E2BF39A60001723EC /* ModelClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModelClass.swift; sourceTree = "<group>"; };
53+
B18792602AA5A0D2006F2CC9 /* CircularView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircularView.swift; sourceTree = "<group>"; };
4054
B19E0B652BF7498700E65974 /* ExampleProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleProgressView.swift; sourceTree = "<group>"; };
4155
B1DFCA502BF4FA3D00F01505 /* ProgressCircle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressCircle.swift; sourceTree = "<group>"; };
4256
B1DFCA522BF4FC7900F01505 /* ArrowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArrowView.swift; sourceTree = "<group>"; };
@@ -74,8 +88,9 @@
7488
isa = PBXGroup;
7589
children = (
7690
B14AB36A2BC40286004B09C4 /* ProgressAnimation */,
77-
B153FD112BFB64BE00AEFE83 /* Examples */,
91+
469963A3290FCE1900DC01AD /* ArrowLeftRightAnimation */,
7892
2BC2D8F228CF3A6F00CAB302 /* SSSwiftUIAnimationsApp.swift */,
93+
B153FD112BFB64BE00AEFE83 /* Examples */,
7994
2BC2D8F428CF3A6F00CAB302 /* ContentView.swift */,
8095
2BC2D8F628CF3A7000CAB302 /* Assets.xcassets */,
8196
2BC2D8F828CF3A7000CAB302 /* Preview Content */,
@@ -91,6 +106,18 @@
91106
path = "Preview Content";
92107
sourceTree = "<group>";
93108
};
109+
469963A3290FCE1900DC01AD /* ArrowLeftRightAnimation */ = {
110+
isa = PBXGroup;
111+
children = (
112+
469963A4290FCE3600DC01AD /* SSLRArrowView.swift */,
113+
4636F36D291E1BD600C8DB5B /* LeftArrow.swift */,
114+
B18792602AA5A0D2006F2CC9 /* CircularView.swift */,
115+
B153FD0B2BFB3C1800AEFE83 /* LRArrowAnimStyle.swift */,
116+
B153FD122BFB71F500AEFE83 /* FilledStrokeCircle.swift */,
117+
);
118+
path = ArrowLeftRightAnimation;
119+
sourceTree = "<group>";
120+
};
94121
B14AB36A2BC40286004B09C4 /* ProgressAnimation */ = {
95122
isa = PBXGroup;
96123
children = (
@@ -110,6 +137,7 @@
110137
isa = PBXGroup;
111138
children = (
112139
B19E0B652BF7498700E65974 /* ExampleProgressView.swift */,
140+
B153FD142BFB7A7900AEFE83 /* ExampleLRArrowView.swift */,
113141
B1F9ED332BFCD85000189871 /* ExamplesList */,
114142
);
115143
path = Examples;
@@ -120,6 +148,7 @@
120148
children = (
121149
B153FD0F2BFB649300AEFE83 /* ExampleListModel.swift */,
122150
B153FD0D2BFB566000AEFE83 /* ExampleListRow.swift */,
151+
B15FD7982C04785700752CEA /* CustomToolBar.swift */,
123152
);
124153
path = ExamplesList;
125154
sourceTree = "<group>";
@@ -201,6 +230,14 @@
201230
B10677FE2BE8D0D400957B4E /* DownArrow.swift in Sources */,
202231
B1098E7D2BD94ED900BC19DD /* WaveView.swift in Sources */,
203232
B19E0B662BF7498700E65974 /* ExampleProgressView.swift in Sources */,
233+
B18792612AA5A0D2006F2CC9 /* CircularView.swift in Sources */,
234+
B153FD152BFB7A7900AEFE83 /* ExampleLRArrowView.swift in Sources */,
235+
2BC2D8F528CF3A6F00CAB302 /* ContentView.swift in Sources */,
236+
4636F36E291E1BD600C8DB5B /* LeftArrow.swift in Sources */,
237+
B15FD7992C04785700752CEA /* CustomToolBar.swift in Sources */,
238+
469963A5290FCE3600DC01AD /* SSLRArrowView.swift in Sources */,
239+
B153FD0C2BFB3C1800AEFE83 /* LRArrowAnimStyle.swift in Sources */,
240+
B153FD132BFB71F500AEFE83 /* FilledStrokeCircle.swift in Sources */,
204241
2BC2D8F328CF3A6F00CAB302 /* SSSwiftUIAnimationsApp.swift in Sources */,
205242
B1DFCA532BF4FC7900F01505 /* ArrowView.swift in Sources */,
206243
B11B983A2BCE9C3F00D76016 /* CheckView.swift in Sources */,
@@ -335,7 +372,7 @@
335372
CODE_SIGN_STYLE = Automatic;
336373
CURRENT_PROJECT_VERSION = 1;
337374
DEVELOPMENT_ASSET_PATHS = "\"SSSwiftUIAnimations/Preview Content\"";
338-
DEVELOPMENT_TEAM = 5LTPQLLYU7;
375+
DEVELOPMENT_TEAM = K7XJG666ZW;
339376
ENABLE_PREVIEWS = YES;
340377
GENERATE_INFOPLIST_FILE = YES;
341378
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
@@ -364,7 +401,7 @@
364401
CODE_SIGN_STYLE = Automatic;
365402
CURRENT_PROJECT_VERSION = 1;
366403
DEVELOPMENT_ASSET_PATHS = "\"SSSwiftUIAnimations/Preview Content\"";
367-
DEVELOPMENT_TEAM = 5LTPQLLYU7;
404+
DEVELOPMENT_TEAM = K7XJG666ZW;
368405
ENABLE_PREVIEWS = YES;
369406
GENERATE_INFOPLIST_FILE = YES;
370407
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
//
2+
// CircularView.swift
3+
// SSSwiftUIAnimations
4+
//
5+
// Created by Mansi Prajapati on 04/09/23.
6+
//
7+
8+
import SwiftUI
9+
10+
struct CircularView: View {
11+
12+
// MARK: - Variables
13+
14+
// End progress of circle stroke
15+
@State private var endProgress: CGFloat = 0.0
16+
17+
// Arrow click state manage
18+
@State private var isArrowClicked = false
19+
20+
// Arrow offset manage
21+
@State private var arrowOffset: CGFloat = 2
22+
23+
// Start progress of circle stroke
24+
@State private var startProgress: CGFloat = 0.0
25+
26+
// State manage for animation completed
27+
@State private var isAnimationCompleted = false
28+
29+
// Manage immediate click
30+
@State private var isDisabled = false
31+
32+
// Arrow size
33+
@State var arrowSize: CGFloat
34+
35+
// Custom animation style
36+
var arrowAnimStyle: LRArrowAnimStyle
37+
38+
// Tap callback on view tap
39+
var tapCallBack: (() -> Void)?
40+
41+
// Animation duration
42+
var animationDuration = 0.4
43+
44+
// MARK: - Body view
45+
var body: some View {
46+
ZStack {
47+
48+
//Empty Stroke circle view
49+
Circle()
50+
.stroke(
51+
arrowAnimStyle.strokeEmptyColor
52+
.opacity(0.3),
53+
lineWidth: arrowAnimStyle.circleStrokeSize
54+
)
55+
.frame(width: arrowAnimStyle.circleSize, height: arrowAnimStyle.circleSize)
56+
57+
// Animate circle stroke from center left to center right of top circle stroke
58+
animatedCircle()
59+
.rotationEffect(.degrees(.zero))
60+
61+
// Animate circle stroke from center left to center right of bottom circle stroke
62+
animatedCircle()
63+
.rotation3DEffect(
64+
.degrees(180), axis: (x: 1, y: .zero, z: .zero)
65+
)
66+
67+
// Left arrow with animation
68+
LeftArrow()
69+
.stroke(style: StrokeStyle(lineWidth: arrowAnimStyle.arrowStrokeSize, lineCap: .round, lineJoin: .round))
70+
.frame(
71+
width: isArrowClicked ? .zero : arrowSize,
72+
height: isArrowClicked ? .zero : arrowSize
73+
)
74+
.offset(x: arrowOffset)
75+
.animation(.linear(duration: 0.3), value: isArrowClicked)
76+
.foregroundColor(arrowAnimStyle.arrowColor)
77+
.onTapGesture {
78+
animateView()
79+
isDisabled = true
80+
81+
// Callback for tap gesture
82+
tapCallBack?()
83+
84+
// For disabling immediate click
85+
DispatchQueue.main.asyncAfter(deadline: .now() + 1.1) {
86+
isDisabled = false
87+
}
88+
}
89+
}.onAppear {
90+
adjustArrowSize()
91+
92+
// Arrow's x offset to adjust arrow in center
93+
arrowOffset = arrowSize / 4.5
94+
}.disabled(isDisabled)
95+
}
96+
97+
// MARK: - Private functions
98+
private func animatedCircle() -> some View {
99+
return FilledStrokeCircle(isAnimationCompleted: $isAnimationCompleted, startProgress: $startProgress, endProgress: $endProgress, arrowStyle: arrowAnimStyle)
100+
}
101+
102+
private func animateView() {
103+
isArrowClicked = true
104+
startProgress = 0.5
105+
isAnimationCompleted = false
106+
arrowOffset = -(arrowAnimStyle.circleSize / 2)
107+
108+
// Animate circle stroke
109+
Timer.scheduledTimer(withTimeInterval: animationDuration * 0.950, repeats: false) { _ in
110+
endProgress = arrowAnimStyle.circleSize / 2
111+
arrowOffset = arrowAnimStyle.circleSize / 2
112+
isAnimationCompleted = true
113+
}
114+
115+
// Adjusting arrow back animation
116+
Timer.scheduledTimer(withTimeInterval: animationDuration * 2, repeats: false) { _ in
117+
withAnimation(Animation.linear(duration: animationDuration * 0.25)) {
118+
startProgress = endProgress
119+
endProgress = .zero
120+
}
121+
}
122+
123+
// Adjust arrow position when animation is completed
124+
Timer.scheduledTimer(withTimeInterval: animationDuration * 2.25, repeats: false) { _ in
125+
withAnimation(Animation.linear(duration: animationDuration * 1.7)) {
126+
isArrowClicked = false
127+
arrowOffset = arrowSize / 4
128+
isAnimationCompleted = false
129+
}
130+
}
131+
132+
// Manage circle stoke animation when animation is about to complete
133+
Timer.scheduledTimer(withTimeInterval: animationDuration * 3, repeats: false) { _ in
134+
isArrowClicked = false
135+
isAnimationCompleted = true
136+
}
137+
}
138+
139+
// Validate and adjust minimum arrow size
140+
private func adjustArrowSize() {
141+
if arrowSize < arrowAnimStyle.circleSize / 4 {
142+
arrowSize = arrowAnimStyle.circleSize / 4
143+
} else if arrowSize >= arrowAnimStyle.circleSize {
144+
arrowSize = arrowAnimStyle.circleSize/1.2
145+
}
146+
}
147+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//
2+
// FilledStrokeCircle.swift
3+
// SSSwiftUIAnimations
4+
//
5+
// Created by Mansi Prajapati on 20/05/24.
6+
//
7+
8+
import SwiftUI
9+
10+
struct FilledStrokeCircle: View {
11+
12+
// MARK: - Variables
13+
@Binding var isAnimationCompleted: Bool
14+
@Binding var startProgress: CGFloat
15+
@Binding var endProgress: CGFloat
16+
let arrowStyle: LRArrowAnimStyle
17+
18+
// MARK: - Body view
19+
var body: some View {
20+
Circle()
21+
.trim(
22+
from: isAnimationCompleted ? startProgress : 0.5,
23+
to: isAnimationCompleted ? endProgress : 0.5
24+
)
25+
.stroke(
26+
arrowStyle.strokeFillColor,
27+
style: StrokeStyle(
28+
lineWidth: arrowStyle.circleStrokeSize,
29+
lineCap: .butt,
30+
lineJoin: .round
31+
)
32+
)
33+
.frame(width: arrowStyle.circleSize, height: arrowStyle.circleSize)
34+
.animation(.linear(duration: 1.6)
35+
.speed(0.08), value: endProgress)
36+
}
37+
}
38+
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
//
2+
// LRArrowAnimStyle.swift
3+
// SSSwiftUIAnimations
4+
//
5+
// Created by Mansi Prajapati on 20/05/24.
6+
//
7+
8+
import SwiftUI
9+
10+
struct LRArrowAnimStyle {
11+
12+
// MARK: - Variables
13+
14+
// Arrow size
15+
var arrowSize: CGFloat
16+
17+
// Circle size
18+
var circleSize: CGFloat
19+
20+
// Empty stroke color for circle
21+
var strokeEmptyColor: Color
22+
23+
// Fill stroke color for circle
24+
var strokeFillColor: Color
25+
26+
// Arrow color
27+
var arrowColor: Color
28+
29+
// Circle stroke size
30+
var circleStrokeSize: CGFloat
31+
32+
// Arrow stroke size
33+
var arrowStrokeSize: CGFloat
34+
35+
init(
36+
arrowSize: CGFloat = 20,
37+
circleSize: CGFloat = 80,
38+
strokeEmptyColor: Color = .brown,
39+
strokeFillColor: Color = .brown,
40+
arrowColor: Color = .brown,
41+
circleStrokeSize: CGFloat = 1,
42+
arrowStrokeSize: CGFloat = 1
43+
) {
44+
self.arrowSize = max(arrowSize, 10)
45+
self.circleSize = max(circleSize, 50)
46+
self.strokeEmptyColor = strokeEmptyColor
47+
self.strokeFillColor = strokeFillColor
48+
self.arrowColor = arrowColor
49+
self.circleStrokeSize = max(min(circleStrokeSize, circleSize/1.2), 1)
50+
self.arrowStrokeSize = max(min(arrowStrokeSize, circleSize/5), 1)
51+
}
52+
}
53+
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
//
2+
// LeftArrow.swift
3+
// SSSwiftUIAnimations
4+
//
5+
// Created by Mansi Prajapati on 11/11/22.
6+
//
7+
8+
import SwiftUI
9+
10+
struct LeftArrow: Shape {
11+
func path(in rect: CGRect) -> Path {
12+
var path = Path()
13+
path.move(to: CGPoint(x: rect.midX, y: rect.minY))
14+
path.addLine(to: CGPoint(x: rect.minX, y: rect.midY))
15+
path.addLine(to: CGPoint(x: rect.midX, y: rect.maxY))
16+
return path
17+
}
18+
}

0 commit comments

Comments
 (0)