From 5496c48d244b8d21eb1a5da691891a0afcdc3507 Mon Sep 17 00:00:00 2001 From: Darshan Gujarati Date: Tue, 29 Nov 2022 12:04:57 +0530 Subject: [PATCH] Send button with arrow animation --- .../project.pbxproj | 12 + .../arrow.imageset/Contents.json | 15 ++ .../arrow.imageset/arrow 2.pdf | Bin 0 -> 6272 bytes .../checkmark.imageset/Contents.json | 15 ++ .../checkmark.imageset/checkmark 1.pdf | Bin 0 -> 2594 bytes .../SendButton/sendButtonView.swift | 212 ++++++++++++++++++ 6 files changed, 254 insertions(+) create mode 100644 SSSwiftUIAnimations/SSSwiftUIAnimations/Assets.xcassets/arrow.imageset/Contents.json create mode 100644 SSSwiftUIAnimations/SSSwiftUIAnimations/Assets.xcassets/arrow.imageset/arrow 2.pdf create mode 100644 SSSwiftUIAnimations/SSSwiftUIAnimations/Assets.xcassets/checkmark.imageset/Contents.json create mode 100644 SSSwiftUIAnimations/SSSwiftUIAnimations/Assets.xcassets/checkmark.imageset/checkmark 1.pdf create mode 100644 SSSwiftUIAnimations/SSSwiftUIAnimations/SendButton/sendButtonView.swift diff --git a/SSSwiftUIAnimations/SSSwiftUIAnimations.xcodeproj/project.pbxproj b/SSSwiftUIAnimations/SSSwiftUIAnimations.xcodeproj/project.pbxproj index 28688ed..3c0ab01 100644 --- a/SSSwiftUIAnimations/SSSwiftUIAnimations.xcodeproj/project.pbxproj +++ b/SSSwiftUIAnimations/SSSwiftUIAnimations.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ 2BC2D8F528CF3A6F00CAB302 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2BC2D8F428CF3A6F00CAB302 /* ContentView.swift */; }; 2BC2D8F728CF3A7000CAB302 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2BC2D8F628CF3A7000CAB302 /* Assets.xcassets */; }; 2BC2D8FA28CF3A7000CAB302 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2BC2D8F928CF3A7000CAB302 /* Preview Assets.xcassets */; }; + 7DD1EBF22935D384008021DF /* sendButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DD1EBF12935D384008021DF /* sendButtonView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -19,6 +20,7 @@ 2BC2D8F428CF3A6F00CAB302 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 2BC2D8F628CF3A7000CAB302 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 2BC2D8F928CF3A7000CAB302 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 7DD1EBF12935D384008021DF /* sendButtonView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = sendButtonView.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -53,6 +55,7 @@ children = ( 2BC2D8F228CF3A6F00CAB302 /* SSSwiftUIAnimationsApp.swift */, 2BC2D8F428CF3A6F00CAB302 /* ContentView.swift */, + 7DD1EBF02935D35A008021DF /* SendButton */, 2BC2D8F628CF3A7000CAB302 /* Assets.xcassets */, 2BC2D8F828CF3A7000CAB302 /* Preview Content */, ); @@ -67,6 +70,14 @@ path = "Preview Content"; sourceTree = ""; }; + 7DD1EBF02935D35A008021DF /* SendButton */ = { + isa = PBXGroup; + children = ( + 7DD1EBF12935D384008021DF /* sendButtonView.swift */, + ); + path = SendButton; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -138,6 +149,7 @@ buildActionMask = 2147483647; files = ( 2BC2D8F528CF3A6F00CAB302 /* ContentView.swift in Sources */, + 7DD1EBF22935D384008021DF /* sendButtonView.swift in Sources */, 2BC2D8F328CF3A6F00CAB302 /* SSSwiftUIAnimationsApp.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/SSSwiftUIAnimations/SSSwiftUIAnimations/Assets.xcassets/arrow.imageset/Contents.json b/SSSwiftUIAnimations/SSSwiftUIAnimations/Assets.xcassets/arrow.imageset/Contents.json new file mode 100644 index 0000000..9c3c97a --- /dev/null +++ b/SSSwiftUIAnimations/SSSwiftUIAnimations/Assets.xcassets/arrow.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "arrow 2.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/SSSwiftUIAnimations/SSSwiftUIAnimations/Assets.xcassets/arrow.imageset/arrow 2.pdf b/SSSwiftUIAnimations/SSSwiftUIAnimations/Assets.xcassets/arrow.imageset/arrow 2.pdf new file mode 100644 index 0000000000000000000000000000000000000000..9cb7761b3fc15c8fdd23a465e1f1876e092cbf09 GIT binary patch literal 6272 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 literal 0 HcmV?d00001 diff --git a/SSSwiftUIAnimations/SSSwiftUIAnimations/Assets.xcassets/checkmark.imageset/Contents.json b/SSSwiftUIAnimations/SSSwiftUIAnimations/Assets.xcassets/checkmark.imageset/Contents.json new file mode 100644 index 0000000..6a984ff --- /dev/null +++ b/SSSwiftUIAnimations/SSSwiftUIAnimations/Assets.xcassets/checkmark.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "checkmark 1.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/SSSwiftUIAnimations/SSSwiftUIAnimations/Assets.xcassets/checkmark.imageset/checkmark 1.pdf b/SSSwiftUIAnimations/SSSwiftUIAnimations/Assets.xcassets/checkmark.imageset/checkmark 1.pdf new file mode 100644 index 0000000000000000000000000000000000000000..7f33b8bcb272902714e47bd8e1ff429f5c9dff2d GIT binary patch literal 2594 zcmb_eX;c$e6b5m@F=z!^N);XPC;@?NBtVrl0f{1nM0P<#G9-~CW0HviwdRQ8030DAVL0=J~U_o*^ zELj3eAt?g7X*&WTsQ?jzWOQ1#77Bnw0wDsD$htA%B0f6A@J)xsu$;4m%R|>@KubkD zXttc2r1ka}i4jN+MuP-@F&BXt5D(@PGVbXLphL>eb@-(8jGYy3FR-c-cLf)iN2K6<@V# zhOMas9^V$&BUvlE{iH}$5f%G2PGekJ-t?m4Uc0p*zo47}r26Ku@GqAVI_13Qa0oI1;$bUq|j*%ms9UnAO8e*NM^8jJHHY4*q^(>XQB;uAJcu9e~3Dfoj* zx9es%>V9a-!?`wJ*>kwyDXlsBMbfB+@s1`nj+c7*CB_0;X#ZILVPnB0KcA(VDcp>i zIolVh#vi#D{KMDLB!z=A`hJ6{fg0yYXYX&QOPihBy5k zf{bqWDw~^o{Xw32Op*yj8P#Vs&J-zo^NnB^Gs0_Ew)e3yA+D^ZYR9{bIO82EYn9KP ztR1wi2P#{;XGFD6z-2nXachG16^pqiYZ`oT+n8Zq9mE#T8XUitkwg(?cg4WM2`!aD zSuuOtvkF&qD9@Wrd=?A_o*TQV{LiWs$5)mktpd#JFWU?|^D2Cu@Vlj({^|>Myyan@ z=3&N9p+2?UeZ76}KU2d?N+#!lKc8Se?w^|9Q~2s_Wsc-WW=}%nx#P|UVq3dsSGn9- zMO)PI^n%5h?mCND@ZH+X^^>cM!fwL3GQuNezQ%TU z!2U2zqj!X}EGIjodq_08zWDeBCex6bgwE^}rCp{vO(Y`v4rW9uU6(V#&4 z8|ljvvohkCdt+N$Hr}lAiBsKu{j$L%EZwm*SgGsoQ}{^vE~MeYZ$DSNB;iR&thv*{iagn2#N7PK{-o>Q*oc!UeD!YL~YWZkwf07?1U`;S_fh5?xB42l_vNTOf^24IRF{$DlD z<#QUxna5QmfS9`KWbnULk3HM>%S7)q$=Jxq=xnYgSc033N6jy3Zzh~JGBW-Ii`c#f zS#%}pIi%`Aj6-dO7^oQ$BoHH*jp{Kb$vV<$Lmaea!Bsm@C)`@OM>h`AIly~gJOv;F zNY2E81`>6KlivEcPv6Cp0D7BD)wxOlgKeARLgQp&Br~hu-*H z9~vtGC6ECFA?Q3Iz$cCa<81Ia$gYN*cGIKSdH6`BFromXv9gHO?7=$DGSusN5MRXg zg%i;_s*g)=7y>j{x|!IJ0#?d-kOIUqK$pvOSc;%kUjbsLtOHqGIrb;uu7%#q1q>S8 z0v7CC5g!c|#CE_QDCAhgbXbY{l0J%GF<9tzSw$ianF`yXLcgM6=X5T@6~ls|zKL=u z9w34w5-^xn*b}5tsT3-R;@XBSfHhF6g-B!y8vX|m4ON<<5S51V^8w_BvNIH-p=*5r zxzo`5HguHxM~-9?^}`^@WD0uiw2p|JD-uI;0Hc@5K*_}`UL5c}%7)JTU g=o>gM0!Ne#VO$LSMRDcG06S!|I|Z0F%`e#hU%OduQ~&?~ literal 0 HcmV?d00001 diff --git a/SSSwiftUIAnimations/SSSwiftUIAnimations/SendButton/sendButtonView.swift b/SSSwiftUIAnimations/SSSwiftUIAnimations/SendButton/sendButtonView.swift new file mode 100644 index 0000000..4a1bd1b --- /dev/null +++ b/SSSwiftUIAnimations/SSSwiftUIAnimations/SendButton/sendButtonView.swift @@ -0,0 +1,212 @@ +// +// sendButton.swift +// SSSwiftUIAnimation +// +// Created by Darshan Gujarati on 11/11/22. +// + +import SwiftUI + +enum SendState { + case origin + case arrowOut + case changeArrowColor + case arrowAtP1 + case arrowAtP2 + case arrowAtP3 + case arrowToTick + case sentOut + case viewCenter + + var animation: Animation { + switch self { + case .arrowOut: return Animation.easeIn(duration: 1.0) + case .arrowAtP1, .arrowAtP3: + return Animation.easeIn(duration: 0.1) + case .arrowToTick: + return Animation.easeIn(duration: 0.0) + default: return Animation.easeIn + } + } + var text: String { + switch self { + case .origin: return "Send" + default: return "" + } + } + var imageRotation: Double { + switch self { + case .arrowAtP1, .changeArrowColor: return -80 + case .arrowAtP2: return 100 + case .arrowAtP3: return 230 + default: return 0 + } + } + var imageName: String { + switch self { + case .origin, .arrowOut, .changeArrowColor, .arrowAtP1, .arrowAtP2, .arrowAtP3: + return "arrow" + case .arrowToTick, .sentOut, .viewCenter: + return "checkmark" + } + } + var imageColor: Color { + switch self { + case .changeArrowColor, .arrowOut, .arrowAtP2, .arrowAtP3: return Color.cyan + default: return Color.white + } + } + var offY: CGFloat { + switch self { + case . arrowOut, .arrowAtP2, .arrowAtP3, .arrowToTick, .viewCenter: return -5 + default: return 0 + } + } +} + +struct sendButtonView: View { + let duration: Double = 3 + var geo: GeometryProxy + var p1: CGPoint { + return CGPoint(x: geo.size.width / 4, y: geo.size.height / 2) + } + var p2: CGPoint { + return CGPoint(x: geo.size.width + 20, y: -geo.size.height) + } + var p3: CGPoint { + return CGPoint(x: geo.size.width / 2, y: geo.size.height / 2) + } + var curvePoint1: CGPoint { + return CGPoint(x: geo.size.width, y: -geo.size.height * 2) + } + var curvePoint2: CGPoint { + return CGPoint(x: geo.size.width, y: geo.size.height * 3) + } + var sizeArrowOut: CGSize { + return CGSize(width: geo.size.height + 10, height: geo.size.height + 10) + } + var sizeSentOut: CGSize { + return CGSize(width: geo.size.height, height: geo.size.height) + } + var path : Path { + var result = Path() + result.move(to: p1) + result.addCurve(to: p2, control1: curvePoint1, control2: curvePoint1) + result.move(to: p2) + result.addCurve(to: p3, control1: curvePoint2, control2: curvePoint2) + return result + } + + @State var frameSize = CGSize(width: 120, height: 60) + @State var isMovingForward = false + @State var animationState: SendState = .origin + + var tMax : CGFloat { isMovingForward ? 1 : 0 } + var opac : Double { isMovingForward ? 1 : 0 } + + var body: some View { + VStack { + ZStack { + Text("Sent!") + .font(.system(size: 20, weight: .bold, design: .rounded)) + .frame(width: animationState == .sentOut ? geo.size.width / 1.1 : 0, height: geo.size.height / 1.5, alignment: .center) + .background(.cyan) + .foregroundColor(.white) + .cornerRadius(12) + .shadow(radius: 3) + .offset(x: animationState == .sentOut ? geo.size.width / 1.71 : 0) + + ZStack { + Text(animationState.text) + .frame(alignment: .center) + .foregroundColor(.white) + .font(.system(size: 20, weight: .bold, design: .rounded)) + .offset(x: 15) + } + .frame(width: frameSize.width, height: frameSize.height) + .background(Color.cyan) + .cornerRadius(animationState == .origin ? geo.size.height / 5 : frameSize.height / 2) + .offset(y: animationState.offY) + .animation(animationState.animation, value: animationState) + + Image(animationState.imageName) + .rotationEffect(Angle(degrees: animationState.imageRotation), anchor: .center) + .foregroundColor(animationState.imageColor) + .modifier(Moving(time: tMax, path: path, start: p1)) + } + } + .shadow(radius: 10) + .offset(x: (animationState == .sentOut || animationState == .viewCenter) ? -geo.size.width / 2.5 : 0) + .onAppear { + frameSize = geo.size + } + .onTapGesture { + Timer.scheduledTimer(withTimeInterval: 0.0, repeats: false) { (Timer) in + self.animationState = .arrowAtP1 + } + Timer.scheduledTimer(withTimeInterval: 0.09, repeats: false) { (Timer) in + self.animationState = .changeArrowColor + } + Timer.scheduledTimer(withTimeInterval: 0.1, repeats: false) { (Timer) in + frameSize = sizeArrowOut + self.animationState = .arrowOut + isMovingForward = true + + DispatchQueue.main.asyncAfter(deadline: .now() + duration + 0.5) { + isMovingForward = false + } + } + Timer.scheduledTimer(withTimeInterval: 0.4, repeats: false) { (Timer) in + self.animationState = .arrowAtP2 + } + Timer.scheduledTimer(withTimeInterval: 0.9, repeats: false) { (Timer) in + self.animationState = .arrowAtP3 + } + Timer.scheduledTimer(withTimeInterval: 1.1, repeats: false) { (Timer) in + self.animationState = .arrowToTick + } + + Timer.scheduledTimer(withTimeInterval: 1.9, repeats: false) { (Timer) in + self.animationState = .viewCenter + } + Timer.scheduledTimer(withTimeInterval: 2.0, repeats: false) { (Timer) in + frameSize = sizeSentOut + self.animationState = .sentOut + } + Timer.scheduledTimer(withTimeInterval: 3, repeats: false) { (Timer) in + frameSize = geo.size + self.animationState = .origin + } + } + .animation(animationState.animation, value: animationState) + } +} + +struct Moving: AnimatableModifier { + var time : CGFloat + let path : Path + let start: CGPoint + + var animatableData: CGFloat { + get { time } + set { time = newValue } + } + + func body(content: Content) -> some View { + content + .position( + path.trimmedPath(from: 0, to: time).currentPoint ?? start + ) + } +} + +struct sendButton_Previews: PreviewProvider { + static var previews: some View { + VStack { + GeometryReader { geo in + sendButtonView(geo: geo) + } + .frame(width: 120, height: 60, alignment: .center) + } + } +}