├── .DS_Store ├── .gitattributes ├── AnimatedWindows ├── .DS_Store ├── AnimatedWindows.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ ├── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcuserdata │ │ │ └── runhuahuang.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ │ └── runhuahuang.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist └── AnimatedWindows │ ├── AnimatedWindows.entitlements │ ├── AnimatedWindowsApp.swift │ ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ ├── batteryColor.colorset │ │ └── Contents.json │ ├── batteryOverlayColor.colorset │ │ └── Contents.json │ └── wind.imageset │ │ ├── Contents.json │ │ └── IMG_4838.jpg │ ├── ChargingView.swift │ ├── ContentView.swift │ ├── Info.plist │ ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json │ └── ViewExtension.swift ├── ChatButtonAnimation.swiftpm ├── .swiftpm │ └── playgrounds │ │ ├── CachedManifest.plist │ │ ├── DocumentThumbnail.plist │ │ ├── DocumentThumbnail.png │ │ └── Workspace.plist ├── BasicViews.swift ├── CircleButtonStyle.swift ├── ContentView.swift ├── MyApp.swift └── Package.swift ├── DoneButtonAnimation.swiftpm ├── .swiftpm │ ├── playgrounds │ │ ├── CachedManifest.plist │ │ ├── DocumentThumbnail.plist │ │ ├── DocumentThumbnail.png │ │ └── Workspace.plist │ └── xcode │ │ ├── package.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcuserdata │ │ │ └── runhuahuang.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ │ └── xcuserdata │ │ └── runhuahuang.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist ├── ContentView.swift ├── MyApp.swift └── Package.swift ├── LICENSE ├── LoadingErrorAndSuccessAnimation.swiftpm ├── .swiftpm │ └── playgrounds │ │ ├── CachedManifest.plist │ │ ├── DocumentThumbnail.plist │ │ ├── DocumentThumbnail.png │ │ └── Workspace.plist ├── BasicViews.swift ├── ColorExtension.swift ├── ContentView.swift ├── MyApp.swift └── Package.swift ├── MediumClapAnimation.swiftpm ├── .swiftpm │ └── playgrounds │ │ ├── CachedManifest.plist │ │ ├── DocumentThumbnail.plist │ │ ├── DocumentThumbnail.png │ │ └── Workspace.plist ├── BasicViews.swift ├── ColorExtension.swift ├── ContentView.swift ├── MyApp.swift └── Package.swift ├── PhotoScaleEffect ├── .DS_Store ├── PhotoScaleEffect.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ ├── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcuserdata │ │ │ └── runhuahuang.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ │ └── runhuahuang.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist └── PhotoScaleEffect │ ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ └── photos │ │ ├── 1.imageset │ │ ├── 1.png │ │ └── Contents.json │ │ ├── 10.imageset │ │ ├── 10.jpg │ │ └── Contents.json │ │ ├── 11.imageset │ │ ├── 11.jpg │ │ └── Contents.json │ │ ├── 12.imageset │ │ ├── 12.jpg │ │ └── Contents.json │ │ ├── 13.imageset │ │ ├── 13.png │ │ └── Contents.json │ │ ├── 14.imageset │ │ ├── 14.png │ │ └── Contents.json │ │ ├── 15.imageset │ │ ├── 15.png │ │ └── Contents.json │ │ ├── 16.imageset │ │ ├── 18.png │ │ └── Contents.json │ │ ├── 17.imageset │ │ ├── 17.png │ │ └── Contents.json │ │ ├── 2.imageset │ │ ├── 2.png │ │ └── Contents.json │ │ ├── 3.imageset │ │ ├── 3.png │ │ └── Contents.json │ │ ├── 4.imageset │ │ ├── 4.png │ │ └── Contents.json │ │ ├── 5.imageset │ │ ├── 5.png │ │ └── Contents.json │ │ ├── 6.imageset │ │ ├── 6.png │ │ └── Contents.json │ │ ├── 7.imageset │ │ ├── 7.jpg │ │ └── Contents.json │ │ ├── 8.imageset │ │ ├── 8.jpg │ │ └── Contents.json │ │ ├── 9.imageset │ │ ├── 9.jpg │ │ └── Contents.json │ │ └── Contents.json │ ├── CloseActionButton.swift │ ├── ContentView.swift │ ├── ImageDetailView.swift │ ├── Model │ └── FlexibleImage.swift │ ├── PhotoScaleEffectApp.swift │ └── Preview Content │ └── Preview Assets.xcassets │ └── Contents.json ├── README.md ├── SubmitButtonAnimation.swiftpm ├── .swiftpm │ └── playgrounds │ │ ├── CachedManifest.plist │ │ ├── DocumentThumbnail.plist │ │ ├── DocumentThumbnail.png │ │ └── Workspace.plist ├── ContentView.swift ├── MyApp.swift └── Package.swift ├── TabLineAnimation ├── .DS_Store ├── Shared │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ ├── BoundsKey.swift │ ├── ContentView.swift │ ├── TabIcon.swift │ └── TabLineAnimationApp.swift ├── TabLineAnimation.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ ├── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcuserdata │ │ │ └── runhuahuang.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ │ └── runhuahuang.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist └── macOS │ └── macOS.entitlements ├── TwitterHashmoji.swiftpm ├── .swiftpm │ ├── playgrounds │ │ ├── CachedManifest.plist │ │ ├── DocumentThumbnail.plist │ │ ├── DocumentThumbnail.png │ │ └── Workspace.plist │ └── xcode │ │ ├── package.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcuserdata │ │ │ └── runhuahuang.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ │ └── xcuserdata │ │ └── runhuahuang.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist ├── AnimationList.swift ├── Assets.xcassets │ ├── Contents.json │ ├── doge.imageset │ │ ├── ABUIABAEGAAg8dnTnQYooqv96QQwpwQ4_AE.png │ │ └── Contents.json │ ├── doge2.imageset │ │ ├── BbsImg_62400881131778_1606876706_s_54187_o_w_300_h_300_45775.png │ │ └── Contents.json │ └── emoji2.imageset │ │ ├── Contents.json │ │ └── PNG image.png ├── ColorExtension.swift ├── MyApp.swift ├── Package.swift └── TwitterAnimationButton.swift ├── TwitterLikeAnimation ├── .DS_Store ├── TwitterLikeAnimation.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ ├── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcuserdata │ │ │ └── runhuahuang.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ │ └── runhuahuang.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist └── TwitterLikeAnimation │ ├── .DS_Store │ ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ ├── darkPink.colorset │ │ └── Contents.json │ ├── lightBlue.colorset │ │ └── Contents.json │ ├── lightGreen.colorset │ │ └── Contents.json │ └── lightRed.colorset │ │ └── Contents.json │ ├── BinaryCircle.swift │ ├── ColorExtension.swift │ ├── ContentView.swift │ ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json │ └── TwitterLikeAnimationApp.swift ├── WaitingDotAnimation.swiftpm ├── .swiftpm │ └── playgrounds │ │ ├── CachedManifest.plist │ │ ├── DocumentThumbnail.plist │ │ ├── DocumentThumbnail.png │ │ └── Workspace.plist ├── ContentView.swift ├── MyApp.swift └── Package.swift ├── cover.png ├── dynamic-island-animation-demo ├── .DS_Store ├── Shared │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ ├── Contents.json │ │ ├── back.imageset │ │ │ ├── Contents.json │ │ │ └── linus-nylund-JP23z_-dA74-unsplash.jpg │ │ └── music.imageset │ │ │ ├── Contents.json │ │ │ └── music.png │ ├── ContentView.swift │ ├── Helper │ │ ├── BackgroundView.swift │ │ └── BatteryView.swift │ ├── Preference.swift │ ├── Views │ │ ├── BasicView.swift │ │ ├── DynamicIslandBatteryView.swift │ │ ├── MusicView.swift │ │ └── TestView.swift │ └── dynamic_island_animation_demoApp.swift ├── dynamic-island-animation-demo.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ ├── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcuserdata │ │ │ └── runhuahuang.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ │ └── runhuahuang.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist └── macOS │ └── macOS.entitlements └── iMessageBubbleAnimation.swiftpm ├── .swiftpm └── playgrounds │ ├── CachedManifest.plist │ ├── DocumentThumbnail.plist │ ├── DocumentThumbnail.png │ └── Workspace.plist ├── ContentView.swift ├── MyApp.swift └── Package.swift /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuangRunHua/swiftui-animation-demo/a1b1af789c13ac08c649157170d341770011d54e/.DS_Store -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /AnimatedWindows/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuangRunHua/swiftui-animation-demo/a1b1af789c13ac08c649157170d341770011d54e/AnimatedWindows/.DS_Store -------------------------------------------------------------------------------- /AnimatedWindows/AnimatedWindows.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /AnimatedWindows/AnimatedWindows.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /AnimatedWindows/AnimatedWindows.xcodeproj/project.xcworkspace/xcuserdata/runhuahuang.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuangRunHua/swiftui-animation-demo/a1b1af789c13ac08c649157170d341770011d54e/AnimatedWindows/AnimatedWindows.xcodeproj/project.xcworkspace/xcuserdata/runhuahuang.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /AnimatedWindows/AnimatedWindows.xcodeproj/xcuserdata/runhuahuang.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | AnimatedWindows.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /AnimatedWindows/AnimatedWindows/AnimatedWindows.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /AnimatedWindows/AnimatedWindows/AnimatedWindowsApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AnimatedWindowsApp.swift 3 | // AnimatedWindows 4 | // 5 | // Created by Runhua Huang on 9/11/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct AnimatedWindowsApp: App { 12 | 13 | var body: some Scene { 14 | WindowGroup { 15 | ContentView() 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /AnimatedWindows/AnimatedWindows/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /AnimatedWindows/AnimatedWindows/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "scale" : "1x", 6 | "size" : "16x16" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "scale" : "2x", 11 | "size" : "16x16" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "scale" : "1x", 16 | "size" : "32x32" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "scale" : "2x", 21 | "size" : "32x32" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "scale" : "1x", 26 | "size" : "128x128" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "scale" : "2x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "scale" : "1x", 36 | "size" : "256x256" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "scale" : "2x", 41 | "size" : "256x256" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "scale" : "1x", 46 | "size" : "512x512" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "scale" : "2x", 51 | "size" : "512x512" 52 | } 53 | ], 54 | "info" : { 55 | "author" : "xcode", 56 | "version" : 1 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /AnimatedWindows/AnimatedWindows/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /AnimatedWindows/AnimatedWindows/Assets.xcassets/batteryColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "display-p3", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.400", 9 | "green" : "0.769", 10 | "red" : "0.396" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /AnimatedWindows/AnimatedWindows/Assets.xcassets/batteryOverlayColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "display-p3", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.561", 9 | "green" : "0.561", 10 | "red" : "0.561" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /AnimatedWindows/AnimatedWindows/Assets.xcassets/wind.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "IMG_4838.jpg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /AnimatedWindows/AnimatedWindows/Assets.xcassets/wind.imageset/IMG_4838.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuangRunHua/swiftui-animation-demo/a1b1af789c13ac08c649157170d341770011d54e/AnimatedWindows/AnimatedWindows/Assets.xcassets/wind.imageset/IMG_4838.jpg -------------------------------------------------------------------------------- /AnimatedWindows/AnimatedWindows/ChargingView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChargingView.swift 3 | // AnimatedWindows 4 | // 5 | // Created by Runhua Huang on 9/11/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ChargingView: View { 11 | var body: some View { 12 | HStack(alignment: .top) { 13 | HStack { 14 | Image("wind") 15 | .resizable() 16 | .aspectRatio(contentMode: .fit) 17 | .cornerRadius(15) 18 | .frame(width: 60, height: 60) 19 | VStack(alignment: .leading) { 20 | Text("起风了") 21 | .foregroundColor(.white) 22 | .fontWeight(.bold) 23 | Text("买辣椒也用券") 24 | .foregroundColor(.gray) 25 | } 26 | } 27 | 28 | Spacer() 29 | 30 | chartView 31 | } 32 | .padding(10) 33 | .background(Color.black) 34 | .cornerRadius(20) 35 | .frame(width: 230) 36 | } 37 | } 38 | 39 | struct ChargingView_Previews: PreviewProvider { 40 | static var previews: some View { 41 | ChargingView() 42 | } 43 | } 44 | 45 | extension ChargingView { 46 | private var chartView: some View { 47 | HStack(spacing: 3) { 48 | RoundedRectangle(cornerRadius: 10) 49 | .frame(width: 3, height: 20) 50 | RoundedRectangle(cornerRadius: 10) 51 | .frame(width: 3, height: 25) 52 | RoundedRectangle(cornerRadius: 10) 53 | .frame(width: 3, height: 30) 54 | RoundedRectangle(cornerRadius: 10) 55 | .frame(width: 3, height: 35) 56 | RoundedRectangle(cornerRadius: 10) 57 | .frame(width: 3, height: 23) 58 | RoundedRectangle(cornerRadius: 10) 59 | .frame(width: 3, height: 15) 60 | } 61 | .foregroundColor(.white) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /AnimatedWindows/AnimatedWindows/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // AnimatedWindows 4 | // 5 | // Created by Runhua Huang on 9/11/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ContentView: View { 11 | 12 | var body: some View { 13 | Button { 14 | ChargingView() 15 | .openInWindow(view: 230, duration: 3, sender: self) 16 | } label: { 17 | Text("Show window") 18 | } 19 | .frame(width: 400, height: 300) 20 | 21 | } 22 | } 23 | 24 | struct ContentView_Previews: PreviewProvider { 25 | static var previews: some View { 26 | ContentView() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /AnimatedWindows/AnimatedWindows/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /AnimatedWindows/AnimatedWindows/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /AnimatedWindows/AnimatedWindows/ViewExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewExtension.swift 3 | // AnimatedWindows 4 | // 5 | // Created by Runhua Huang on 9/11/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | extension View { 11 | @discardableResult 12 | func openInWindow(view width:CGFloat, duration: CGFloat, sender: Any?) -> NSWindow? { 13 | if let screenWidth = NSScreen.main?.frame.size.width, let screenHeight = NSScreen.main?.frame.size.height { 14 | 15 | let controller = NSHostingController(rootView: self) 16 | let win = NSWindow(contentViewController: controller) 17 | win.titlebarAppearsTransparent = true 18 | win.makeKeyAndOrderFront(sender) 19 | win.backgroundColor = .clear 20 | win.animationBehavior = .none 21 | win.styleMask.remove(.titled) 22 | /// Set the original position of window. 23 | win.setFrameOrigin(CGPoint(x: screenWidth/2 - width/2, y: screenHeight)) 24 | /// 此处设置width与height只影响window的大小 25 | /// 在前面已经设置backgroundColor为clear故对视图没有影响。 26 | win.animator().setFrame(CGRect(x: screenWidth/2 - width/2, y: screenHeight-100, width: 300, height: 10), display: true, animate: true) 27 | 28 | /// After 3 seconds, move the window outside windows automatically. 29 | /// If the y set too large, the animation will become wired. 30 | DispatchQueue.main.asyncAfter(deadline: .now() + duration) { 31 | win.animator().setFrame(CGRect(x: screenWidth/2 - width/2, y: screenHeight + 100, width: 300, height: 10), display: true, animate: true) 32 | } 33 | 34 | DispatchQueue.main.asyncAfter(deadline: .now() + duration + 0.2) { 35 | win.close() 36 | } 37 | 38 | return win 39 | } 40 | return nil 41 | } 42 | } 43 | 44 | -------------------------------------------------------------------------------- /ChatButtonAnimation.swiftpm/.swiftpm/playgrounds/CachedManifest.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CachedManifest 6 | 7 | manifestData 8 | 9 | eyJkZXBlbmRlbmNpZXMiOltdLCJkaXNwbGF5TmFtZSI6IkNoYXRCdXR0b25B 10 | bmltYXRpb24iLCJwYWNrYWdlS2luZCI6eyJyb290Ijp7fX0sInBsYXRmb3Jt 11 | cyI6W3sib3B0aW9ucyI6W10sInBsYXRmb3JtTmFtZSI6ImlvcyIsInZlcnNp 12 | b24iOiIxNS4yIn1dLCJwcm9kdWN0cyI6W3sibmFtZSI6IkNoYXRCdXR0b25B 13 | bmltYXRpb24iLCJzZXR0aW5ncyI6W3siZGlzcGxheVZlcnNpb24iOlsiMS4w 14 | Il19LHsiYnVuZGxlVmVyc2lvbiI6WyIxIl19LHsiaU9TQXBwSW5mbyI6W3si 15 | YWNjZW50Q29sb3IiOnsicHJlc2V0Q29sb3IiOnsicHJlc2V0Q29sb3IiOnsi 16 | cmF3VmFsdWUiOiJwaW5rIn19fSwiYXBwSWNvbiI6eyJwbGFjZWhvbGRlciI6 17 | eyJpY29uIjp7InJhd1ZhbHVlIjoic3RhciJ9fX0sImNhcGFiaWxpdGllcyI6 18 | W10sInN1cHBvcnRlZERldmljZUZhbWlsaWVzIjpbInBhZCIsInBob25lIl0s 19 | InN1cHBvcnRlZEludGVyZmFjZU9yaWVudGF0aW9ucyI6W3sicG9ydHJhaXQi 20 | Ont9fSx7ImxhbmRzY2FwZVJpZ2h0Ijp7fX0seyJsYW5kc2NhcGVMZWZ0Ijp7 21 | fX0seyJwb3J0cmFpdFVwc2lkZURvd24iOnsiY29uZGl0aW9uIjp7ImRldmlj 22 | ZUZhbWlsaWVzIjpbInBhZCJdfX19XX1dfV0sInRhcmdldHMiOlsiQXBwTW9k 23 | dWxlIl0sInR5cGUiOnsiZXhlY3V0YWJsZSI6bnVsbH19XSwidGFyZ2V0TWFw 24 | Ijp7IkFwcE1vZHVsZSI6eyJkZXBlbmRlbmNpZXMiOltdLCJleGNsdWRlIjpb 25 | XSwibmFtZSI6IkFwcE1vZHVsZSIsInBhdGgiOiIuIiwicmVzb3VyY2VzIjpb 26 | XSwic2V0dGluZ3MiOltdLCJ0eXBlIjoiZXhlY3V0YWJsZSJ9fSwidGFyZ2V0 27 | cyI6W3siZGVwZW5kZW5jaWVzIjpbXSwiZXhjbHVkZSI6W10sIm5hbWUiOiJB 28 | cHBNb2R1bGUiLCJwYXRoIjoiLiIsInJlc291cmNlcyI6W10sInNldHRpbmdz 29 | IjpbXSwidHlwZSI6ImV4ZWN1dGFibGUifV0sInRvb2xzVmVyc2lvbiI6eyJf 30 | dmVyc2lvbiI6IjUuNi4wIn19 31 | 32 | manifestHash 33 | 34 | o3YjO2T+50xT6bdBMwoT8/2uh0R6qqZ78KHm9eJ2fSw= 35 | 36 | schemaVersion 37 | 4 38 | swiftPMVersionString 39 | 5.6.0-dev 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /ChatButtonAnimation.swiftpm/.swiftpm/playgrounds/DocumentThumbnail.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | DocumentThumbnailConfiguration 6 | 7 | accentColorHash 8 | 9 | pnpByLx51dqRe1BR8fDT9a60tjuiRrNUapYe96PH2TE= 10 | 11 | appIconHash 12 | 13 | Ul7KHVCJ29y7ZwDZEMXgvCP7qiPuAmwOIkwrRUkOXyk= 14 | 15 | thumbnailIsPrerendered 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /ChatButtonAnimation.swiftpm/.swiftpm/playgrounds/DocumentThumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuangRunHua/swiftui-animation-demo/a1b1af789c13ac08c649157170d341770011d54e/ChatButtonAnimation.swiftpm/.swiftpm/playgrounds/DocumentThumbnail.png -------------------------------------------------------------------------------- /ChatButtonAnimation.swiftpm/.swiftpm/playgrounds/Workspace.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | AppSettings 6 | 7 | appIconPlaceholderGlyphName 8 | star 9 | appSettingsVersion 10 | 1 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /ChatButtonAnimation.swiftpm/BasicViews.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct PlusButton: View { 4 | 5 | @State private var showOtherButtons = false 6 | @Binding var showAnimation: Bool 7 | 8 | var body: some View { 9 | Button(action: { 10 | withAnimation(.interactiveSpring(response: 0.5, dampingFraction: 0.7, blendDuration: 1)) { 11 | self.showOtherButtons.toggle() 12 | self.showAnimation.toggle() 13 | } 14 | }) { 15 | BasicImage(systemName: "plus") 16 | } 17 | .buttonStyle(CircleButtonStyle()) 18 | .rotationEffect(showOtherButtons ? Angle(degrees: -135): Angle(degrees: 0)) 19 | } 20 | } 21 | 22 | struct RecordButton: View { 23 | var body: some View { 24 | Button(action: { 25 | 26 | }) { 27 | BasicImage(systemName: "video") 28 | } 29 | .buttonStyle(CircleButtonStyle()) 30 | } 31 | } 32 | 33 | struct CameraButton: View { 34 | var body: some View { 35 | Button(action: { 36 | 37 | }) { 38 | BasicImage(systemName: "camera") 39 | } 40 | .buttonStyle(CircleButtonStyle()) 41 | } 42 | } 43 | 44 | struct PhotoButton: View { 45 | var body: some View { 46 | Button(action: { 47 | 48 | }) { 49 | BasicImage(systemName: "photo") 50 | } 51 | .buttonStyle(CircleButtonStyle()) 52 | } 53 | } 54 | 55 | struct TextView: View { 56 | @State private var text: String = "" 57 | var body: some View { 58 | TextField("", text: $text) 59 | .placeholder(when: text.isEmpty) { 60 | Text("Message") 61 | .foregroundColor(.white) 62 | .padding(.leading, 25) 63 | } 64 | .textFieldStyle(OvalTextFieldStyle()) 65 | } 66 | } 67 | 68 | struct BackgroundView: View { 69 | var body: some View { 70 | RoundedRectangle(cornerRadius: 40) 71 | .foregroundColor(Color.indigo) 72 | .shadow(color: .indigo, radius: 40, x: 0, y: 10) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /ChatButtonAnimation.swiftpm/CircleButtonStyle.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct CircleButtonStyle: ButtonStyle { 4 | func makeBody(configuration: Configuration) -> some View { 5 | configuration.label 6 | .padding() 7 | .background(.quaternary, in: Capsule()) 8 | .opacity(configuration.isPressed ? 0.5 : 1) 9 | .frame(width: 40, height: 40) 10 | .cornerRadius(20) 11 | .foregroundColor(.white) 12 | } 13 | } 14 | 15 | struct BasicImage: View { 16 | var systemName: String 17 | var body: some View { 18 | Image(systemName: systemName) 19 | .resizable() 20 | .aspectRatio(contentMode: .fill) 21 | .foregroundColor(.white) 22 | .frame(width: 17, height: 17) 23 | } 24 | } 25 | 26 | struct OvalTextFieldStyle: TextFieldStyle { 27 | func _body(configuration: TextField) -> some View { 28 | configuration 29 | .padding(12) 30 | .background(.quaternary, in: Capsule()) 31 | .foregroundColor(.white) 32 | .cornerRadius(40) 33 | } 34 | } 35 | 36 | extension View { 37 | func placeholder( 38 | when shouldShow: Bool, 39 | alignment: Alignment = .leading, 40 | @ViewBuilder placeholder: () -> Content) -> some View { 41 | ZStack(alignment: alignment) { 42 | self 43 | placeholder().opacity(shouldShow ? 1 : 0) 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /ChatButtonAnimation.swiftpm/ContentView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct ContentView: View { 4 | 5 | @State private var showAnimation = false 6 | 7 | var body: some View { 8 | ZStack(alignment: .leading) { 9 | BackgroundView() 10 | .frame(width: 250, height: 80) 11 | 12 | HStack(spacing: 17) { 13 | PlusButton(showAnimation: $showAnimation) 14 | 15 | ZStack(alignment: .leading) { 16 | TextView() 17 | .frame(width: 157, height: 50) 18 | .rotationEffect(showAnimation ? Angle(degrees: -90): Angle(degrees: 0), anchor: UnitPoint(x: -0.2, y: 0.5)) 19 | .opacity(showAnimation ? 0: 1) 20 | HStack(spacing: 17) { 21 | RecordButton() 22 | CameraButton() 23 | PhotoButton() 24 | } 25 | .rotationEffect(showAnimation ? Angle(degrees: 0): Angle(degrees: 90), anchor: UnitPoint(x: -0.2, y: 0.5)) 26 | .opacity(showAnimation ? 1: 0) 27 | } 28 | } 29 | .padding() 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /ChatButtonAnimation.swiftpm/MyApp.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | @main 4 | struct MyApp: App { 5 | var body: some Scene { 6 | WindowGroup { 7 | ContentView() 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /ChatButtonAnimation.swiftpm/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.6 2 | 3 | // WARNING: 4 | // This file is automatically generated. 5 | // Do not edit it by hand because the contents will be replaced. 6 | 7 | import PackageDescription 8 | import AppleProductTypes 9 | 10 | let package = Package( 11 | name: "ChatButtonAnimation", 12 | platforms: [ 13 | .iOS("15.2") 14 | ], 15 | products: [ 16 | .iOSApplication( 17 | name: "ChatButtonAnimation", 18 | targets: ["AppModule"], 19 | displayVersion: "1.0", 20 | bundleVersion: "1", 21 | appIcon: .placeholder(icon: .star), 22 | accentColor: .presetColor(.pink), 23 | supportedDeviceFamilies: [ 24 | .pad, 25 | .phone 26 | ], 27 | supportedInterfaceOrientations: [ 28 | .portrait, 29 | .landscapeRight, 30 | .landscapeLeft, 31 | .portraitUpsideDown(.when(deviceFamilies: [.pad])) 32 | ] 33 | ) 34 | ], 35 | targets: [ 36 | .executableTarget( 37 | name: "AppModule", 38 | path: "." 39 | ) 40 | ] 41 | ) 42 | -------------------------------------------------------------------------------- /DoneButtonAnimation.swiftpm/.swiftpm/playgrounds/CachedManifest.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CachedManifest 6 | 7 | manifestData 8 | 9 | eyJkZXBlbmRlbmNpZXMiOltdLCJkaXNwbGF5TmFtZSI6IkRvbmUgQnV0dG9u 10 | IEFuaW1hdGlvbiIsInBhY2thZ2VLaW5kIjp7InJvb3QiOnt9fSwicGxhdGZv 11 | cm1zIjpbeyJvcHRpb25zIjpbXSwicGxhdGZvcm1OYW1lIjoiaW9zIiwidmVy 12 | c2lvbiI6IjE1LjIifV0sInByb2R1Y3RzIjpbeyJuYW1lIjoiRG9uZSBCdXR0 13 | b24gQW5pbWF0aW9uIiwic2V0dGluZ3MiOlt7ImRpc3BsYXlWZXJzaW9uIjpb 14 | IjEuMCJdfSx7ImJ1bmRsZVZlcnNpb24iOlsiMSJdfSx7ImlPU0FwcEluZm8i 15 | Olt7ImFjY2VudENvbG9yIjp7InByZXNldENvbG9yIjp7InByZXNldENvbG9y 16 | Ijp7InJhd1ZhbHVlIjoidGVhbCJ9fX0sImFwcEljb24iOnsicGxhY2Vob2xk 17 | ZXIiOnsiaWNvbiI6eyJyYXdWYWx1ZSI6ImJpY3ljbGUifX19LCJjYXBhYmls 18 | aXRpZXMiOltdLCJzdXBwb3J0ZWREZXZpY2VGYW1pbGllcyI6WyJwYWQiLCJw 19 | aG9uZSJdLCJzdXBwb3J0ZWRJbnRlcmZhY2VPcmllbnRhdGlvbnMiOlt7InBv 20 | cnRyYWl0Ijp7fX0seyJsYW5kc2NhcGVSaWdodCI6e319LHsibGFuZHNjYXBl 21 | TGVmdCI6e319LHsicG9ydHJhaXRVcHNpZGVEb3duIjp7ImNvbmRpdGlvbiI6 22 | eyJkZXZpY2VGYW1pbGllcyI6WyJwYWQiXX19fV19XX1dLCJ0YXJnZXRzIjpb 23 | IkFwcE1vZHVsZSJdLCJ0eXBlIjp7ImV4ZWN1dGFibGUiOm51bGx9fV0sInRh 24 | cmdldE1hcCI6eyJBcHBNb2R1bGUiOnsiZGVwZW5kZW5jaWVzIjpbXSwiZXhj 25 | bHVkZSI6W10sIm5hbWUiOiJBcHBNb2R1bGUiLCJwYXRoIjoiLiIsInJlc291 26 | cmNlcyI6W10sInNldHRpbmdzIjpbXSwidHlwZSI6ImV4ZWN1dGFibGUifX0s 27 | InRhcmdldHMiOlt7ImRlcGVuZGVuY2llcyI6W10sImV4Y2x1ZGUiOltdLCJu 28 | YW1lIjoiQXBwTW9kdWxlIiwicGF0aCI6Ii4iLCJyZXNvdXJjZXMiOltdLCJz 29 | ZXR0aW5ncyI6W10sInR5cGUiOiJleGVjdXRhYmxlIn1dLCJ0b29sc1ZlcnNp 30 | b24iOnsiX3ZlcnNpb24iOiI1LjYuMCJ9fQ== 31 | 32 | manifestHash 33 | 34 | UOkEivcswMswinIxHPu2PDntbLoM09FeFRSCb8/pW1Y= 35 | 36 | schemaVersion 37 | 4 38 | swiftPMVersionString 39 | 5.6.0-dev 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /DoneButtonAnimation.swiftpm/.swiftpm/playgrounds/DocumentThumbnail.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | DocumentThumbnailConfiguration 6 | 7 | accentColorHash 8 | 9 | JlU5ms+Ze8KtU0bsogWosZRedIMdSqxxyvvP4qc/CT4= 10 | 11 | appIconHash 12 | 13 | 0pr4ouFg3YZ/5Fp19wv4BbG2wc+SAXz1PN8dXPOQyRY= 14 | 15 | thumbnailIsPrerendered 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /DoneButtonAnimation.swiftpm/.swiftpm/playgrounds/DocumentThumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuangRunHua/swiftui-animation-demo/a1b1af789c13ac08c649157170d341770011d54e/DoneButtonAnimation.swiftpm/.swiftpm/playgrounds/DocumentThumbnail.png -------------------------------------------------------------------------------- /DoneButtonAnimation.swiftpm/.swiftpm/playgrounds/Workspace.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | AppSettings 6 | 7 | appIconPlaceholderGlyphName 8 | bicycle 9 | appSettingsVersion 10 | 1 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /DoneButtonAnimation.swiftpm/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /DoneButtonAnimation.swiftpm/.swiftpm/xcode/package.xcworkspace/xcuserdata/runhuahuang.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuangRunHua/swiftui-animation-demo/a1b1af789c13ac08c649157170d341770011d54e/DoneButtonAnimation.swiftpm/.swiftpm/xcode/package.xcworkspace/xcuserdata/runhuahuang.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /DoneButtonAnimation.swiftpm/.swiftpm/xcode/xcuserdata/runhuahuang.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Done Button Animation.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /DoneButtonAnimation.swiftpm/ContentView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct ContentView: View { 4 | 5 | @State private var checkButtonTapped: Bool = false 6 | @State private var showCheckmark: Bool = false 7 | @State private var backButtonDisabled: Bool = true 8 | @State private var scaleRate: Double = 1.0 9 | 10 | var body: some View { 11 | 12 | VStack { 13 | ZStack { 14 | Circle() 15 | .foregroundColor(.green) 16 | .frame(width: 98, height: 98) 17 | .cornerRadius(50) 18 | 19 | Circle() 20 | .trim(from: 0, to: self.checkButtonTapped ? 1: 0) 21 | .stroke( 22 | Color.green, 23 | style: StrokeStyle(lineWidth: 5, lineCap: .round) 24 | ) 25 | .background( 26 | Color.white 27 | ) 28 | .rotationEffect(Angle(degrees: -90)) 29 | .frame(width: 100, height: 100) 30 | .cornerRadius(50) 31 | .scaleEffect(scaleRate) 32 | 33 | /// Checkmark View. 34 | Checkmark() 35 | .trim(from: 0, to: self.showCheckmark ? 1: 0) 36 | .stroke(Color.white, style: StrokeStyle(lineWidth: 5, lineCap: .round)) 37 | .frame(width: 25, height: 25) 38 | .offset(x: 7, y: 0) 39 | .opacity(self.showCheckmark ? 1: 0) 40 | } 41 | .padding() 42 | 43 | 44 | Button { 45 | withAnimation(.easeInOut(duration: 1)) { 46 | self.checkButtonTapped.toggle() 47 | } 48 | DispatchQueue.main.asyncAfter(deadline: .now() + 1) { 49 | withAnimation(.easeInOut(duration: 0.5)) { 50 | self.scaleRate = 0.001 51 | } 52 | } 53 | DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { 54 | withAnimation(.easeInOut(duration: 0.5)) { 55 | self.showCheckmark.toggle() 56 | } 57 | } 58 | self.backButtonDisabled = false 59 | } label: { 60 | Text("Tap to view the animation") 61 | .foregroundColor(.green) 62 | } 63 | .padding() 64 | .disabled(!self.backButtonDisabled) 65 | 66 | Button { 67 | withAnimation(.easeInOut(duration: 0.5)) { 68 | self.showCheckmark.toggle() 69 | } 70 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { 71 | withAnimation(.easeInOut(duration: 0.5)) { 72 | self.scaleRate = 1.0 73 | } 74 | } 75 | DispatchQueue.main.asyncAfter(deadline: .now() + 1) { 76 | withAnimation(.easeInOut(duration: 1)) { 77 | self.checkButtonTapped.toggle() 78 | } 79 | } 80 | self.backButtonDisabled = true 81 | } label: { 82 | Text("Back") 83 | .foregroundColor(.green) 84 | } 85 | .disabled(self.backButtonDisabled) 86 | } 87 | } 88 | } 89 | 90 | struct Checkmark: Shape { 91 | func path(in rect: CGRect) -> Path { 92 | let width = rect.size.width 93 | let height = rect.size.height 94 | 95 | var path = Path() 96 | path.move(to: .init(x: -0.5 * width, y: 0.5 * height)) 97 | path.addLine(to: .init(x: 0 * width, y: 1.0 * height)) 98 | path.addLine(to: .init(x: 1 * width, y: 0 * height)) 99 | return path 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /DoneButtonAnimation.swiftpm/MyApp.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | @main 4 | struct MyApp: App { 5 | var body: some Scene { 6 | WindowGroup { 7 | ContentView() 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /DoneButtonAnimation.swiftpm/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.6 2 | 3 | // WARNING: 4 | // This file is automatically generated. 5 | // Do not edit it by hand because the contents will be replaced. 6 | 7 | import PackageDescription 8 | import AppleProductTypes 9 | 10 | let package = Package( 11 | name: "Done Button Animation", 12 | platforms: [ 13 | .iOS("15.2") 14 | ], 15 | products: [ 16 | .iOSApplication( 17 | name: "Done Button Animation", 18 | targets: ["AppModule"], 19 | displayVersion: "1.0", 20 | bundleVersion: "1", 21 | appIcon: .placeholder(icon: .bicycle), 22 | accentColor: .presetColor(.teal), 23 | supportedDeviceFamilies: [ 24 | .pad, 25 | .phone 26 | ], 27 | supportedInterfaceOrientations: [ 28 | .portrait, 29 | .landscapeRight, 30 | .landscapeLeft, 31 | .portraitUpsideDown(.when(deviceFamilies: [.pad])) 32 | ] 33 | ) 34 | ], 35 | targets: [ 36 | .executableTarget( 37 | name: "AppModule", 38 | path: "." 39 | ) 40 | ] 41 | ) 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LoadingErrorAndSuccessAnimation.swiftpm/.swiftpm/playgrounds/CachedManifest.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CachedManifest 6 | 7 | manifestData 8 | 9 | eyJkZXBlbmRlbmNpZXMiOltdLCJkaXNwbGF5TmFtZSI6IkxvYWRpbmdFcnJv 10 | ckFuZFN1Y2Nlc3NBbmltYXRpb24iLCJwYWNrYWdlS2luZCI6eyJyb290Ijp7 11 | fX0sInBsYXRmb3JtcyI6W3sib3B0aW9ucyI6W10sInBsYXRmb3JtTmFtZSI6 12 | ImlvcyIsInZlcnNpb24iOiIxNS4yIn1dLCJwcm9kdWN0cyI6W3sibmFtZSI6 13 | IkxvYWRpbmdFcnJvckFuZFN1Y2Nlc3NBbmltYXRpb24iLCJzZXR0aW5ncyI6 14 | W3siZGlzcGxheVZlcnNpb24iOlsiMS4wIl19LHsiYnVuZGxlVmVyc2lvbiI6 15 | WyIxIl19LHsiaU9TQXBwSW5mbyI6W3siYWNjZW50Q29sb3IiOnsicHJlc2V0 16 | Q29sb3IiOnsicHJlc2V0Q29sb3IiOnsicmF3VmFsdWUiOiJjeWFuIn19fSwi 17 | YXBwSWNvbiI6eyJwbGFjZWhvbGRlciI6eyJpY29uIjp7InJhd1ZhbHVlIjoi 18 | Y2xvdWQifX19LCJjYXBhYmlsaXRpZXMiOltdLCJzdXBwb3J0ZWREZXZpY2VG 19 | YW1pbGllcyI6WyJwYWQiLCJwaG9uZSJdLCJzdXBwb3J0ZWRJbnRlcmZhY2VP 20 | cmllbnRhdGlvbnMiOlt7InBvcnRyYWl0Ijp7fX0seyJsYW5kc2NhcGVSaWdo 21 | dCI6e319LHsibGFuZHNjYXBlTGVmdCI6e319LHsicG9ydHJhaXRVcHNpZGVE 22 | b3duIjp7ImNvbmRpdGlvbiI6eyJkZXZpY2VGYW1pbGllcyI6WyJwYWQiXX19 23 | fV19XX1dLCJ0YXJnZXRzIjpbIkFwcE1vZHVsZSJdLCJ0eXBlIjp7ImV4ZWN1 24 | dGFibGUiOm51bGx9fV0sInRhcmdldE1hcCI6eyJBcHBNb2R1bGUiOnsiZGVw 25 | ZW5kZW5jaWVzIjpbXSwiZXhjbHVkZSI6W10sIm5hbWUiOiJBcHBNb2R1bGUi 26 | LCJwYXRoIjoiLiIsInJlc291cmNlcyI6W10sInNldHRpbmdzIjpbXSwidHlw 27 | ZSI6ImV4ZWN1dGFibGUifX0sInRhcmdldHMiOlt7ImRlcGVuZGVuY2llcyI6 28 | W10sImV4Y2x1ZGUiOltdLCJuYW1lIjoiQXBwTW9kdWxlIiwicGF0aCI6Ii4i 29 | LCJyZXNvdXJjZXMiOltdLCJzZXR0aW5ncyI6W10sInR5cGUiOiJleGVjdXRh 30 | YmxlIn1dLCJ0b29sc1ZlcnNpb24iOnsiX3ZlcnNpb24iOiI1LjYuMCJ9fQ== 31 | 32 | manifestHash 33 | 34 | 480iBbVCWVju0zsAorenrZ0Vk6RBCNv8Qu0wV0u3kz8= 35 | 36 | schemaVersion 37 | 4 38 | swiftPMVersionString 39 | 5.6.0-dev 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /LoadingErrorAndSuccessAnimation.swiftpm/.swiftpm/playgrounds/DocumentThumbnail.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | DocumentThumbnailConfiguration 6 | 7 | accentColorHash 8 | 9 | MFe13OMFARMJ8HDqD5bDNSWxDg9LDdv8oq4TvGw4ZwM= 10 | 11 | appIconHash 12 | 13 | VmgQELdT4avlLESdCqspGyjxgIo6kba66qcmiDuq1LA= 14 | 15 | thumbnailIsPrerendered 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /LoadingErrorAndSuccessAnimation.swiftpm/.swiftpm/playgrounds/DocumentThumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuangRunHua/swiftui-animation-demo/a1b1af789c13ac08c649157170d341770011d54e/LoadingErrorAndSuccessAnimation.swiftpm/.swiftpm/playgrounds/DocumentThumbnail.png -------------------------------------------------------------------------------- /LoadingErrorAndSuccessAnimation.swiftpm/.swiftpm/playgrounds/Workspace.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | AppSettings 6 | 7 | appIconPlaceholderGlyphName 8 | cloud 9 | appSettingsVersion 10 | 1 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /LoadingErrorAndSuccessAnimation.swiftpm/BasicViews.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct ExclamationMark: View { 4 | var body: some View { 5 | VStack(spacing: 0) { 6 | VerticalLine() 7 | .trim(from: 0, to: 1) 8 | .stroke(Color.white, style: StrokeStyle(lineWidth: 5, lineCap: .round)) 9 | .frame(width: 20, height: 20) 10 | .offset(x: 10, y: 0) 11 | Circle() 12 | .foregroundColor(.white) 13 | .frame(width: 7, height: 7) 14 | } 15 | } 16 | } 17 | 18 | struct RightArrow: View { 19 | var body: some View { 20 | HStack(spacing: -50) { 21 | Line() 22 | .trim(from: 0, to: 1) 23 | .stroke(Color.darkGreen, style: StrokeStyle(lineWidth: 5, lineCap: .round)) 24 | Arrow() 25 | .trim(from: 0, to: 1) 26 | .stroke(Color.darkGreen, style: StrokeStyle(lineWidth: 5, lineCap: .round)) 27 | } 28 | .frame(width: 50, height: 50) 29 | } 30 | } 31 | 32 | 33 | struct Arrow: Shape { 34 | func path(in rect: CGRect) -> Path { 35 | let width = rect.size.width 36 | let height = rect.size.height 37 | 38 | var path = Path() 39 | path.move(to: .init(x: 0.25 * width, y: -0.25 * height)) 40 | path.addLine(to: .init(x: 0.5 * width, y: 0 * height)) 41 | path.addLine(to: .init(x: 0.25 * width, y: 0.25 * height)) 42 | return path 43 | } 44 | } 45 | 46 | struct Line: Shape { 47 | func path(in rect: CGRect) -> Path { 48 | let width = rect.size.width 49 | let height = rect.size.height 50 | 51 | var path = Path() 52 | path.move(to: .init(x: -0.25 * width, y: 0 * height)) 53 | path.addLine(to: .init(x: 0.5 * width, y: 0 * height)) 54 | return path 55 | } 56 | } 57 | 58 | struct VerticalLine: Shape { 59 | func path(in rect: CGRect) -> Path { 60 | let width = rect.size.width 61 | let height = rect.size.height 62 | 63 | var path = Path() 64 | path.move(to: .init(x: 0 * width, y: -0.5 * height)) 65 | path.addLine(to: .init(x: 0 * width, y: 0.5 * height)) 66 | return path 67 | } 68 | } 69 | 70 | 71 | struct Shake: GeometryEffect { 72 | var amount: CGFloat = 10 73 | var shakesPerUnit = 3 74 | var animatableData: CGFloat 75 | 76 | func effectValue(size: CGSize) -> ProjectionTransform { 77 | ProjectionTransform(CGAffineTransform(translationX: 78 | amount * sin(animatableData * .pi * CGFloat(shakesPerUnit)), 79 | y: 0)) 80 | } 81 | } 82 | 83 | struct Checkmark: Shape { 84 | func path(in rect: CGRect) -> Path { 85 | let width = rect.size.width 86 | let height = rect.size.height 87 | 88 | var path = Path() 89 | path.move(to: .init(x: -0.5 * width, y: 0.5 * height)) 90 | path.addLine(to: .init(x: 0 * width, y: 1.0 * height)) 91 | path.addLine(to: .init(x: 1 * width, y: 0 * height)) 92 | return path 93 | } 94 | } 95 | 96 | -------------------------------------------------------------------------------- /LoadingErrorAndSuccessAnimation.swiftpm/ColorExtension.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | extension Color { 4 | static let lightPink = Color.init(red: 0.99, green: 0.94, blue: 0.95) 5 | static let lightGreen = Color.init(red: 0.43, green: 0.75, blue: 0.58) 6 | static let darkGreen = Color.init(red: 0.31, green: 0.65, blue: 0.75) 7 | } 8 | -------------------------------------------------------------------------------- /LoadingErrorAndSuccessAnimation.swiftpm/ContentView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct ContentView: View { 4 | 5 | @State private var buttonTapped: Bool = false 6 | @State private var loading: Bool = false 7 | @State private var loadingError: Bool = false 8 | @State private var shakeAnimationBegins: Bool = false 9 | @State private var reloadButtonAppear: Bool = false 10 | @State private var reloadButtonTapped: Bool = false 11 | @State private var reloading: Bool = false 12 | @State private var showCheckmark: Bool = false 13 | 14 | var body: some View { 15 | ZStack { 16 | Color.lightPink 17 | VStack { 18 | Button { 19 | withAnimation(.easeInOut(duration: 0.3)) { 20 | self.buttonTapped.toggle() 21 | } 22 | withAnimation(.easeInOut(duration: 3)) { 23 | self.loading.toggle() 24 | } 25 | DispatchQueue.main.asyncAfter(deadline: .now() + 2) { 26 | withAnimation(.easeInOut(duration: 0.3)) { 27 | self.loadingError.toggle() 28 | } 29 | } 30 | DispatchQueue.main.asyncAfter(deadline: .now() + 2) { 31 | withAnimation(.easeInOut(duration: 0.3)) { 32 | self.shakeAnimationBegins.toggle() 33 | } 34 | } 35 | DispatchQueue.main.asyncAfter(deadline: .now() + 2.3) { 36 | withAnimation(.easeInOut(duration: 0.1)) { 37 | self.reloadButtonAppear.toggle() 38 | } 39 | } 40 | 41 | } label: { 42 | ZStack { 43 | RoundedRectangle(cornerRadius: 40) 44 | .frame(width: self.buttonTapped ? 80: 170, height: 80) 45 | .foregroundColor(self.showCheckmark ? .lightGreen: self.reloadButtonTapped ? .white: self.loadingError ? .orange: .white) 46 | .shadow(radius: 5, x: 0, y: 5) 47 | 48 | ExclamationMark() 49 | .rotationEffect(self.loadingError ? Angle(degrees: 0): Angle(degrees: -90)) 50 | .scaleEffect(self.reloadButtonTapped ? 0.001: self.loadingError ? 1: 0.001) 51 | .opacity(self.reloadButtonTapped ? 0: self.loadingError ? 1: 0) 52 | .offset(x: 0, y: 5) 53 | 54 | Circle() 55 | .trim(from: 0, to: 0.8) 56 | .stroke(Color.darkGreen, style: StrokeStyle(lineWidth: 5, lineCap: .round)) 57 | .frame(width: 30, height: 30) 58 | .rotationEffect(self.reloading ? Angle(degrees: 1575*2): self.loading ? Angle(degrees: 1575): Angle(degrees: -135)) 59 | .scaleEffect(self.reloadButtonTapped ? 1: self.loadingError ? 0.001: self.buttonTapped ? 1: 0.001) 60 | .opacity(self.showCheckmark ? 0: self.reloadButtonTapped ? 1: self.loadingError ? 0: self.buttonTapped ? 1: 0) 61 | 62 | RightArrow() 63 | .offset(x: 20, y: 24) 64 | .rotationEffect(Angle(degrees: self.buttonTapped ? 135: 0)) 65 | .opacity(self.loadingError ? 0: self.buttonTapped ? 0: 1) 66 | 67 | Checkmark() 68 | .trim(from: 0, to: self.showCheckmark ? 1: 0) 69 | .stroke(Color.white, style: StrokeStyle(lineWidth: 5, lineCap: .round)) 70 | .frame(width: 25, height: 25) 71 | .offset(x: 7, y: 0) 72 | .opacity(self.showCheckmark ? 1: 0) 73 | } 74 | .modifier(self.shakeAnimationBegins ? Shake(animatableData: CGFloat(1)): Shake(animatableData: CGFloat(0))) 75 | } 76 | 77 | Button { 78 | withAnimation(.easeInOut(duration: 0.2)) { 79 | self.reloadButtonTapped.toggle() 80 | } 81 | withAnimation(.easeInOut(duration: 3)) { 82 | self.reloading.toggle() 83 | } 84 | DispatchQueue.main.asyncAfter(deadline: .now() + 2) { 85 | withAnimation(.easeInOut(duration: 0.4)) { 86 | self.showCheckmark.toggle() 87 | } 88 | } 89 | self.reloadButtonAppear.toggle() 90 | } label: { 91 | Text("Reload") 92 | .foregroundColor(.orange) 93 | } 94 | .padding() 95 | .opacity(self.reloadButtonAppear ? 1: 0) 96 | } 97 | 98 | } 99 | } 100 | } 101 | 102 | -------------------------------------------------------------------------------- /LoadingErrorAndSuccessAnimation.swiftpm/MyApp.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | @main 4 | struct MyApp: App { 5 | var body: some Scene { 6 | WindowGroup { 7 | ContentView() 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /LoadingErrorAndSuccessAnimation.swiftpm/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.6 2 | 3 | // WARNING: 4 | // This file is automatically generated. 5 | // Do not edit it by hand because the contents will be replaced. 6 | 7 | import PackageDescription 8 | import AppleProductTypes 9 | 10 | let package = Package( 11 | name: "LoadingErrorAndSuccessAnimation", 12 | platforms: [ 13 | .iOS("15.2") 14 | ], 15 | products: [ 16 | .iOSApplication( 17 | name: "LoadingErrorAndSuccessAnimation", 18 | targets: ["AppModule"], 19 | displayVersion: "1.0", 20 | bundleVersion: "1", 21 | appIcon: .placeholder(icon: .cloud), 22 | accentColor: .presetColor(.cyan), 23 | supportedDeviceFamilies: [ 24 | .pad, 25 | .phone 26 | ], 27 | supportedInterfaceOrientations: [ 28 | .portrait, 29 | .landscapeRight, 30 | .landscapeLeft, 31 | .portraitUpsideDown(.when(deviceFamilies: [.pad])) 32 | ] 33 | ) 34 | ], 35 | targets: [ 36 | .executableTarget( 37 | name: "AppModule", 38 | path: "." 39 | ) 40 | ] 41 | ) 42 | -------------------------------------------------------------------------------- /MediumClapAnimation.swiftpm/.swiftpm/playgrounds/CachedManifest.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CachedManifest 6 | 7 | manifestData 8 | 9 | eyJkZXBlbmRlbmNpZXMiOltdLCJkaXNwbGF5TmFtZSI6Ik1lZGl1bSBDbGFw 10 | IEFuaW1hdGlvbiIsInBhY2thZ2VLaW5kIjp7InJvb3QiOnt9fSwicGxhdGZv 11 | cm1zIjpbeyJvcHRpb25zIjpbXSwicGxhdGZvcm1OYW1lIjoiaW9zIiwidmVy 12 | c2lvbiI6IjE1LjIifV0sInByb2R1Y3RzIjpbeyJuYW1lIjoiTWVkaXVtIENs 13 | YXAgQW5pbWF0aW9uIiwic2V0dGluZ3MiOlt7ImRpc3BsYXlWZXJzaW9uIjpb 14 | IjEuMCJdfSx7ImJ1bmRsZVZlcnNpb24iOlsiMSJdfSx7ImlPU0FwcEluZm8i 15 | Olt7ImFjY2VudENvbG9yIjp7InByZXNldENvbG9yIjp7InByZXNldENvbG9y 16 | Ijp7InJhd1ZhbHVlIjoidGVhbCJ9fX0sImFwcEljb24iOnsicGxhY2Vob2xk 17 | ZXIiOnsiaWNvbiI6eyJyYXdWYWx1ZSI6InNtaWxleSJ9fX0sImNhcGFiaWxp 18 | dGllcyI6W10sInN1cHBvcnRlZERldmljZUZhbWlsaWVzIjpbInBhZCIsInBo 19 | b25lIl0sInN1cHBvcnRlZEludGVyZmFjZU9yaWVudGF0aW9ucyI6W3sicG9y 20 | dHJhaXQiOnt9fSx7ImxhbmRzY2FwZVJpZ2h0Ijp7fX0seyJsYW5kc2NhcGVM 21 | ZWZ0Ijp7fX0seyJwb3J0cmFpdFVwc2lkZURvd24iOnsiY29uZGl0aW9uIjp7 22 | ImRldmljZUZhbWlsaWVzIjpbInBhZCJdfX19XX1dfV0sInRhcmdldHMiOlsi 23 | QXBwTW9kdWxlIl0sInR5cGUiOnsiZXhlY3V0YWJsZSI6bnVsbH19XSwidGFy 24 | Z2V0TWFwIjp7IkFwcE1vZHVsZSI6eyJkZXBlbmRlbmNpZXMiOltdLCJleGNs 25 | dWRlIjpbXSwibmFtZSI6IkFwcE1vZHVsZSIsInBhdGgiOiIuIiwicmVzb3Vy 26 | Y2VzIjpbXSwic2V0dGluZ3MiOltdLCJ0eXBlIjoiZXhlY3V0YWJsZSJ9fSwi 27 | dGFyZ2V0cyI6W3siZGVwZW5kZW5jaWVzIjpbXSwiZXhjbHVkZSI6W10sIm5h 28 | bWUiOiJBcHBNb2R1bGUiLCJwYXRoIjoiLiIsInJlc291cmNlcyI6W10sInNl 29 | dHRpbmdzIjpbXSwidHlwZSI6ImV4ZWN1dGFibGUifV0sInRvb2xzVmVyc2lv 30 | biI6eyJfdmVyc2lvbiI6IjUuNi4wIn19 31 | 32 | manifestHash 33 | 34 | M2pp5I+sGPEBqVaKwPS43CxFZQfO5VXHPK1tSO98awE= 35 | 36 | schemaVersion 37 | 4 38 | swiftPMVersionString 39 | 5.6.0-dev 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /MediumClapAnimation.swiftpm/.swiftpm/playgrounds/DocumentThumbnail.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | DocumentThumbnailConfiguration 6 | 7 | accentColorHash 8 | 9 | JlU5ms+Ze8KtU0bsogWosZRedIMdSqxxyvvP4qc/CT4= 10 | 11 | appIconHash 12 | 13 | 9CQED1r9iEbylIKTrPH0maUC0IH0mTe7TPJS0Uaj0Z4= 14 | 15 | thumbnailIsPrerendered 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /MediumClapAnimation.swiftpm/.swiftpm/playgrounds/DocumentThumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuangRunHua/swiftui-animation-demo/a1b1af789c13ac08c649157170d341770011d54e/MediumClapAnimation.swiftpm/.swiftpm/playgrounds/DocumentThumbnail.png -------------------------------------------------------------------------------- /MediumClapAnimation.swiftpm/.swiftpm/playgrounds/Workspace.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | AppSettings 6 | 7 | appIconPlaceholderGlyphName 8 | smiley 9 | appSettingsVersion 10 | 1 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /MediumClapAnimation.swiftpm/BasicViews.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct ClapButton: View { 4 | 5 | @Binding var showBackgroundShadow: Bool 6 | @Binding var clapButtonTapped: Bool 7 | 8 | var body: some View { 9 | ZStack { 10 | Circle() 11 | .stroke(self.clapButtonTapped ? Color.pinkRed: self.showBackgroundShadow ? Color.pinkRed: Color.gray, style: StrokeStyle(lineWidth: 4)) 12 | .background(Color.white) 13 | .cornerRadius(60) 14 | .frame(width: 120, height: 120) 15 | .shadow(color: .pinkRed, radius: self.showBackgroundShadow ? 5: 0) 16 | 17 | Image(systemName: self.clapButtonTapped ? "hands.clap.fill": "hands.clap") 18 | .resizable() 19 | .aspectRatio(contentMode: .fit) 20 | .foregroundColor(self.clapButtonTapped ? .pinkRed: self.showBackgroundShadow ? .pinkRed: .gray) 21 | .frame(width: 70, height: 70) 22 | } 23 | } 24 | } 25 | 26 | struct PlusLikeView: View { 27 | var body: some View { 28 | ZStack { 29 | Circle() 30 | .foregroundColor(.pinkRed) 31 | .frame(width: 70, height: 70) 32 | 33 | Text("+1") 34 | .foregroundColor(.white) 35 | .font(.system(size: 25)) 36 | } 37 | } 38 | } 39 | 40 | 41 | struct FireWorks: View { 42 | var body: some View { 43 | HStack(spacing: 20) { 44 | RoundedRectangle(cornerRadius: 3) 45 | .foregroundColor(.darkGreen) 46 | .frame(width: 7, height: 7) 47 | Triangle() 48 | .foregroundColor(.darkOrange) 49 | .frame(width: 7, height: 10) 50 | .offset(x: 0, y: -30) 51 | } 52 | } 53 | } 54 | 55 | struct Triangle: Shape { 56 | func path(in rect: CGRect) -> Path { 57 | let width = rect.size.width 58 | let height = rect.size.height 59 | 60 | var path = Path() 61 | path.move(to: .init(x: -1 * width, y: 0 * height)) 62 | path.addLine(to: .init(x: 1 * width, y: 0 * height)) 63 | path.addLine(to: .init(x: 0 * width, y: 2 * height)) 64 | path.addLine(to: .init(x: -0.2 * width, y: 2.4 * height)) 65 | path.addLine(to: .init(x: 0.2 * width, y: 2.4 * height)) 66 | return path 67 | } 68 | } 69 | 70 | struct FireWorksView: View { 71 | var body: some View { 72 | ZStack { 73 | FireWorks() 74 | .offset(x: 0, y: -70) 75 | .rotationEffect(Angle(degrees: 30), anchor: .topTrailing) 76 | 77 | FireWorks() 78 | .offset(x: 0, y: -60) 79 | .rotationEffect(Angle(degrees: 120), anchor: .topTrailing) 80 | 81 | FireWorks() 82 | .offset(x: 0, y: -80) 83 | .rotationEffect(Angle(degrees: 190), anchor: .topTrailing) 84 | FireWorks() 85 | .offset(x: 0, y: -100) 86 | .rotationEffect(Angle(degrees: 260), anchor: .topTrailing) 87 | FireWorks() 88 | .offset(x: 0, y: -95) 89 | .rotationEffect(Angle(degrees: 320), anchor: .topTrailing) 90 | } 91 | } 92 | } 93 | 94 | 95 | struct MyPreviewProvider_Previews: PreviewProvider { 96 | static var previews: some View { 97 | FireWorksView() 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /MediumClapAnimation.swiftpm/ColorExtension.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | extension Color { 4 | static let pinkRed = Color.init(red: 0.84, green: 0.21, blue: 0.43) 5 | static let darkGreen = Color.init(red: 0.36, green: 0.6, blue: 0.66) 6 | static let darkOrange = Color.init(red: 0.89, green: 0.54, blue: 0.54) 7 | } 8 | -------------------------------------------------------------------------------- /MediumClapAnimation.swiftpm/ContentView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct ContentView: View { 4 | /// When on hover, show button's shadow. 5 | @State private var showBackgroundShadow: Bool = false 6 | /// The marker of whether the button is clapped. 7 | @State private var clapButtonTapped: Bool = false 8 | /// The scale rate of the button. 9 | @State private var scaleRate: CGFloat = 1.0 10 | /// If already clapped, disable the scale effect when tap button again. 11 | @State private var clapped: Bool = false 12 | /// Hide the plus like `+1` view. 13 | @State private var showPlusView: Bool = false 14 | /// Move and hide the plus view after 0.3 secs 15 | @State private var moveAndHidePlusView: Bool = false 16 | /// Hide the fireworks view 17 | @State private var hideFireWorks: Bool = false 18 | 19 | var body: some View { 20 | ZStack { 21 | /// The whole background color is set to white. 22 | Color.white 23 | /// Main actor. 24 | ClapButton(showBackgroundShadow: $showBackgroundShadow, clapButtonTapped: $clapButtonTapped) 25 | .scaleEffect(self.clapped ? 1: self.scaleRate) 26 | .onTapGesture { 27 | withAnimation(.easeInOut(duration: 0.1)) { 28 | self.clapButtonTapped.toggle() 29 | self.scaleRate = 1.2 30 | self.showPlusView.toggle() 31 | } 32 | /// After 0.1 sec, scale the button back. 33 | DispatchQueue.main.asyncAfter(deadline: .now()+0.1) { 34 | withAnimation(.easeInOut(duration: 0.3)) { 35 | self.scaleRate = 1.0 36 | self.clapped.toggle() 37 | } 38 | } 39 | 40 | /// If already clapped we need to toggle the flag first 41 | /// so that when next clap action happens the plus one view 42 | /// will appears. 43 | if clapped { 44 | self.moveAndHidePlusView = false 45 | self.hideFireWorks = false 46 | } else { 47 | /// After 0.1 sec, hide the fireworks view 48 | DispatchQueue.main.asyncAfter(deadline: .now()+0.1) { 49 | withAnimation(.easeInOut(duration: 0.3)) { 50 | self.hideFireWorks = true 51 | } 52 | } 53 | /// After 0.3 sec, hide the plus one view. 54 | DispatchQueue.main.asyncAfter(deadline: .now()+0.6) { 55 | withAnimation(.easeInOut(duration: 0.3)) { 56 | self.moveAndHidePlusView = true 57 | } 58 | } 59 | } 60 | 61 | } 62 | .onHover { newValue in 63 | withAnimation(.easeInOut(duration: 0.1)) { 64 | self.showBackgroundShadow = newValue 65 | } 66 | } 67 | 68 | PlusLikeView() 69 | .offset(x: 0, y: self.moveAndHidePlusView ? -180: self.showPlusView ? -150: -130) 70 | .opacity(self.moveAndHidePlusView ? 0: self.showPlusView ? 1: 0) 71 | 72 | FireWorksView() 73 | .scaleEffect(self.hideFireWorks ? 1.3: self.scaleRate) 74 | .opacity(self.hideFireWorks ? 0: self.clapButtonTapped ? 1: 0) 75 | 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /MediumClapAnimation.swiftpm/MyApp.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | @main 4 | struct MyApp: App { 5 | var body: some Scene { 6 | WindowGroup { 7 | ContentView() 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /MediumClapAnimation.swiftpm/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.6 2 | 3 | // WARNING: 4 | // This file is automatically generated. 5 | // Do not edit it by hand because the contents will be replaced. 6 | 7 | import PackageDescription 8 | import AppleProductTypes 9 | 10 | let package = Package( 11 | name: "Medium Clap Animation", 12 | platforms: [ 13 | .iOS("15.2") 14 | ], 15 | products: [ 16 | .iOSApplication( 17 | name: "Medium Clap Animation", 18 | targets: ["AppModule"], 19 | displayVersion: "1.0", 20 | bundleVersion: "1", 21 | appIcon: .placeholder(icon: .smiley), 22 | accentColor: .presetColor(.teal), 23 | supportedDeviceFamilies: [ 24 | .pad, 25 | .phone 26 | ], 27 | supportedInterfaceOrientations: [ 28 | .portrait, 29 | .landscapeRight, 30 | .landscapeLeft, 31 | .portraitUpsideDown(.when(deviceFamilies: [.pad])) 32 | ] 33 | ) 34 | ], 35 | targets: [ 36 | .executableTarget( 37 | name: "AppModule", 38 | path: "." 39 | ) 40 | ] 41 | ) 42 | -------------------------------------------------------------------------------- /PhotoScaleEffect/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuangRunHua/swiftui-animation-demo/a1b1af789c13ac08c649157170d341770011d54e/PhotoScaleEffect/.DS_Store -------------------------------------------------------------------------------- /PhotoScaleEffect/PhotoScaleEffect.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /PhotoScaleEffect/PhotoScaleEffect.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /PhotoScaleEffect/PhotoScaleEffect.xcodeproj/project.xcworkspace/xcuserdata/runhuahuang.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuangRunHua/swiftui-animation-demo/a1b1af789c13ac08c649157170d341770011d54e/PhotoScaleEffect/PhotoScaleEffect.xcodeproj/project.xcworkspace/xcuserdata/runhuahuang.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /PhotoScaleEffect/PhotoScaleEffect.xcodeproj/xcuserdata/runhuahuang.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | PhotoScaleEffect.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /PhotoScaleEffect/PhotoScaleEffect/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /PhotoScaleEffect/PhotoScaleEffect/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /PhotoScaleEffect/PhotoScaleEffect/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /PhotoScaleEffect/PhotoScaleEffect/Assets.xcassets/photos/1.imageset/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuangRunHua/swiftui-animation-demo/a1b1af789c13ac08c649157170d341770011d54e/PhotoScaleEffect/PhotoScaleEffect/Assets.xcassets/photos/1.imageset/1.png -------------------------------------------------------------------------------- /PhotoScaleEffect/PhotoScaleEffect/Assets.xcassets/photos/1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "1.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /PhotoScaleEffect/PhotoScaleEffect/Assets.xcassets/photos/10.imageset/10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuangRunHua/swiftui-animation-demo/a1b1af789c13ac08c649157170d341770011d54e/PhotoScaleEffect/PhotoScaleEffect/Assets.xcassets/photos/10.imageset/10.jpg -------------------------------------------------------------------------------- /PhotoScaleEffect/PhotoScaleEffect/Assets.xcassets/photos/10.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "10.jpg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /PhotoScaleEffect/PhotoScaleEffect/Assets.xcassets/photos/11.imageset/11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuangRunHua/swiftui-animation-demo/a1b1af789c13ac08c649157170d341770011d54e/PhotoScaleEffect/PhotoScaleEffect/Assets.xcassets/photos/11.imageset/11.jpg -------------------------------------------------------------------------------- /PhotoScaleEffect/PhotoScaleEffect/Assets.xcassets/photos/11.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "11.jpg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /PhotoScaleEffect/PhotoScaleEffect/Assets.xcassets/photos/12.imageset/12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuangRunHua/swiftui-animation-demo/a1b1af789c13ac08c649157170d341770011d54e/PhotoScaleEffect/PhotoScaleEffect/Assets.xcassets/photos/12.imageset/12.jpg -------------------------------------------------------------------------------- /PhotoScaleEffect/PhotoScaleEffect/Assets.xcassets/photos/12.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "12.jpg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /PhotoScaleEffect/PhotoScaleEffect/Assets.xcassets/photos/13.imageset/13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuangRunHua/swiftui-animation-demo/a1b1af789c13ac08c649157170d341770011d54e/PhotoScaleEffect/PhotoScaleEffect/Assets.xcassets/photos/13.imageset/13.png -------------------------------------------------------------------------------- /PhotoScaleEffect/PhotoScaleEffect/Assets.xcassets/photos/13.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "13.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /PhotoScaleEffect/PhotoScaleEffect/Assets.xcassets/photos/14.imageset/14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuangRunHua/swiftui-animation-demo/a1b1af789c13ac08c649157170d341770011d54e/PhotoScaleEffect/PhotoScaleEffect/Assets.xcassets/photos/14.imageset/14.png -------------------------------------------------------------------------------- /PhotoScaleEffect/PhotoScaleEffect/Assets.xcassets/photos/14.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "14.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /PhotoScaleEffect/PhotoScaleEffect/Assets.xcassets/photos/15.imageset/15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuangRunHua/swiftui-animation-demo/a1b1af789c13ac08c649157170d341770011d54e/PhotoScaleEffect/PhotoScaleEffect/Assets.xcassets/photos/15.imageset/15.png -------------------------------------------------------------------------------- /PhotoScaleEffect/PhotoScaleEffect/Assets.xcassets/photos/15.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "15.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /PhotoScaleEffect/PhotoScaleEffect/Assets.xcassets/photos/16.imageset/18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuangRunHua/swiftui-animation-demo/a1b1af789c13ac08c649157170d341770011d54e/PhotoScaleEffect/PhotoScaleEffect/Assets.xcassets/photos/16.imageset/18.png -------------------------------------------------------------------------------- /PhotoScaleEffect/PhotoScaleEffect/Assets.xcassets/photos/16.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "18.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /PhotoScaleEffect/PhotoScaleEffect/Assets.xcassets/photos/17.imageset/17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuangRunHua/swiftui-animation-demo/a1b1af789c13ac08c649157170d341770011d54e/PhotoScaleEffect/PhotoScaleEffect/Assets.xcassets/photos/17.imageset/17.png -------------------------------------------------------------------------------- /PhotoScaleEffect/PhotoScaleEffect/Assets.xcassets/photos/17.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "17.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /PhotoScaleEffect/PhotoScaleEffect/Assets.xcassets/photos/2.imageset/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuangRunHua/swiftui-animation-demo/a1b1af789c13ac08c649157170d341770011d54e/PhotoScaleEffect/PhotoScaleEffect/Assets.xcassets/photos/2.imageset/2.png -------------------------------------------------------------------------------- /PhotoScaleEffect/PhotoScaleEffect/Assets.xcassets/photos/2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "2.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /PhotoScaleEffect/PhotoScaleEffect/Assets.xcassets/photos/3.imageset/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuangRunHua/swiftui-animation-demo/a1b1af789c13ac08c649157170d341770011d54e/PhotoScaleEffect/PhotoScaleEffect/Assets.xcassets/photos/3.imageset/3.png -------------------------------------------------------------------------------- /PhotoScaleEffect/PhotoScaleEffect/Assets.xcassets/photos/3.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "3.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /PhotoScaleEffect/PhotoScaleEffect/Assets.xcassets/photos/4.imageset/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuangRunHua/swiftui-animation-demo/a1b1af789c13ac08c649157170d341770011d54e/PhotoScaleEffect/PhotoScaleEffect/Assets.xcassets/photos/4.imageset/4.png -------------------------------------------------------------------------------- /PhotoScaleEffect/PhotoScaleEffect/Assets.xcassets/photos/4.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "4.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /PhotoScaleEffect/PhotoScaleEffect/Assets.xcassets/photos/5.imageset/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuangRunHua/swiftui-animation-demo/a1b1af789c13ac08c649157170d341770011d54e/PhotoScaleEffect/PhotoScaleEffect/Assets.xcassets/photos/5.imageset/5.png -------------------------------------------------------------------------------- /PhotoScaleEffect/PhotoScaleEffect/Assets.xcassets/photos/5.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "5.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /PhotoScaleEffect/PhotoScaleEffect/Assets.xcassets/photos/6.imageset/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuangRunHua/swiftui-animation-demo/a1b1af789c13ac08c649157170d341770011d54e/PhotoScaleEffect/PhotoScaleEffect/Assets.xcassets/photos/6.imageset/6.png -------------------------------------------------------------------------------- /PhotoScaleEffect/PhotoScaleEffect/Assets.xcassets/photos/6.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "6.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /PhotoScaleEffect/PhotoScaleEffect/Assets.xcassets/photos/7.imageset/7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuangRunHua/swiftui-animation-demo/a1b1af789c13ac08c649157170d341770011d54e/PhotoScaleEffect/PhotoScaleEffect/Assets.xcassets/photos/7.imageset/7.jpg -------------------------------------------------------------------------------- /PhotoScaleEffect/PhotoScaleEffect/Assets.xcassets/photos/7.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "7.jpg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /PhotoScaleEffect/PhotoScaleEffect/Assets.xcassets/photos/8.imageset/8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuangRunHua/swiftui-animation-demo/a1b1af789c13ac08c649157170d341770011d54e/PhotoScaleEffect/PhotoScaleEffect/Assets.xcassets/photos/8.imageset/8.jpg -------------------------------------------------------------------------------- /PhotoScaleEffect/PhotoScaleEffect/Assets.xcassets/photos/8.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "8.jpg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /PhotoScaleEffect/PhotoScaleEffect/Assets.xcassets/photos/9.imageset/9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuangRunHua/swiftui-animation-demo/a1b1af789c13ac08c649157170d341770011d54e/PhotoScaleEffect/PhotoScaleEffect/Assets.xcassets/photos/9.imageset/9.jpg -------------------------------------------------------------------------------- /PhotoScaleEffect/PhotoScaleEffect/Assets.xcassets/photos/9.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "9.jpg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /PhotoScaleEffect/PhotoScaleEffect/Assets.xcassets/photos/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /PhotoScaleEffect/PhotoScaleEffect/CloseActionButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CloseActionButton.swift 3 | // PhotoScaleEffect 4 | // 5 | // Created by Runhua Huang on 9/14/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct CloseActionButton: View { 11 | var label: LocalizedStringKey 12 | var systemImage: String 13 | var action: () -> Void 14 | 15 | var body: some View { 16 | Button(action: action) { 17 | Image(systemName: systemImage) 18 | .font(Font.title.bold()) 19 | .imageScale(.large) 20 | .frame(width: 44, height: 44) 21 | .padding() 22 | .contentShape(Rectangle()) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /PhotoScaleEffect/PhotoScaleEffect/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // PhotoScaleEffect 4 | // 5 | // Created by Runhua Huang on 9/14/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ContentView: View { 11 | 12 | private let threeColumnGrid: [GridItem] = [ 13 | GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible()) 14 | ] 15 | 16 | @State private var currentDetailImageID: FlexibleImage.ID? 17 | 18 | @Namespace private var shapeTransition 19 | 20 | var body: some View { 21 | GeometryReader { geo in 22 | ZStack { 23 | ScrollView { 24 | LazyVGrid(columns: threeColumnGrid, spacing: 2) { 25 | ForEach(FlexibleImage.images.indices, id: \.self) { idx in 26 | let presenting = currentDetailImageID == FlexibleImage.images[idx].id 27 | 28 | Button { 29 | withAnimation(.spring(response: 0.45, dampingFraction: 0.9)) { 30 | self.currentDetailImageID = FlexibleImage.images[idx].id 31 | } 32 | } label: { 33 | ImageDetailView(image: FlexibleImage.images[idx], displayMode: presenting ? .original: .thumbnail) 34 | .matchedGeometryEffect(id: FlexibleImage.images[idx].id, in: shapeTransition, isSource: !presenting) 35 | } 36 | .frame(width: geo.size.width/3, height: geo.size.width/3) 37 | } 38 | } 39 | .opacity(self.currentDetailImageID == nil ? 1: 0) 40 | } 41 | 42 | 43 | if let currentDetailImageID = currentDetailImageID { 44 | if let currentImage = FlexibleImage.images.first(where: { $0.id == currentDetailImageID}) { 45 | ImageDetailView(image: currentImage, displayMode: .original) 46 | .matchedGeometryEffect(id: currentDetailImageID, in: shapeTransition) 47 | .frame(width: geo.size.width, height: geo.size.height) 48 | } 49 | 50 | HStack { 51 | Spacer() 52 | VStack { 53 | CloseActionButton(label: "Close", systemImage: "xmark.circle.fill", action: deselect) 54 | .scaleEffect(self.currentDetailImageID != nil ? 1 : 0.5) 55 | .opacity(self.currentDetailImageID != nil ? 1 : 0) 56 | Spacer() 57 | } 58 | } 59 | } 60 | } 61 | .navigationTitle("Photos") 62 | } 63 | } 64 | 65 | private func deselect() { 66 | withAnimation(.linear) { 67 | self.currentDetailImageID = nil 68 | } 69 | } 70 | } 71 | 72 | struct ContentView_Previews: PreviewProvider { 73 | static var previews: some View { 74 | ContentView() 75 | .preferredColorScheme(.dark) 76 | } 77 | } 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /PhotoScaleEffect/PhotoScaleEffect/ImageDetailView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageDetailView.swift 3 | // PhotoScaleEffect 4 | // 5 | // Created by Runhua Huang on 9/14/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ImageDetailView: View { 11 | 12 | let image: FlexibleImage 13 | var displayMode: DisplayMode 14 | 15 | enum DisplayMode { 16 | case thumbnail 17 | case original 18 | } 19 | 20 | var body: some View { 21 | ZStack { 22 | GeometryReader { geo in 23 | Image(image.imageName) 24 | .resizable() 25 | .aspectRatio(contentMode: displayMode == .original ? .fit: .fill) 26 | .frame(width: geo.size.width, height: geo.size.height) 27 | } 28 | } 29 | .compositingGroup() 30 | .clipped() 31 | } 32 | } 33 | 34 | struct ImageDetailView_Previews: PreviewProvider { 35 | static var previews: some View { 36 | Group { 37 | ImageDetailView(image: FlexibleImage.images[3], displayMode: .thumbnail) 38 | .frame(width: 180, height: 180) 39 | .previewDisplayName("Thumbnail") 40 | 41 | ImageDetailView(image: FlexibleImage.images[3], displayMode: .original) 42 | .previewDisplayName("Original") 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /PhotoScaleEffect/PhotoScaleEffect/Model/FlexibleImage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FlexibleImage.swift 3 | // PhotoScaleEffect 4 | // 5 | // Created by Runhua Huang on 9/14/22. 6 | // 7 | 8 | import Foundation 9 | 10 | struct FlexibleImage: Identifiable { 11 | var id: UUID = UUID() 12 | var imageName: String 13 | var showDetail: Bool = false 14 | 15 | static var images: [FlexibleImage] = [ 16 | FlexibleImage(imageName: "1"), 17 | FlexibleImage(imageName: "2"), 18 | FlexibleImage(imageName: "3"), 19 | FlexibleImage(imageName: "4"), 20 | FlexibleImage(imageName: "5"), 21 | FlexibleImage(imageName: "6"), 22 | FlexibleImage(imageName: "7"), 23 | FlexibleImage(imageName: "8"), 24 | FlexibleImage(imageName: "9"), 25 | FlexibleImage(imageName: "10"), 26 | FlexibleImage(imageName: "11"), 27 | FlexibleImage(imageName: "12"), 28 | FlexibleImage(imageName: "13"), 29 | FlexibleImage(imageName: "14"), 30 | FlexibleImage(imageName: "15"), 31 | FlexibleImage(imageName: "16"), 32 | FlexibleImage(imageName: "17"), 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /PhotoScaleEffect/PhotoScaleEffect/PhotoScaleEffectApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PhotoScaleEffectApp.swift 3 | // PhotoScaleEffect 4 | // 5 | // Created by Runhua Huang on 9/14/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct PhotoScaleEffectApp: App { 12 | var body: some Scene { 13 | WindowGroup { 14 | ContentView() 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /PhotoScaleEffect/PhotoScaleEffect/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # swiftui-animation-demo 2 | ![](https://github.com/HuangRunHua/swiftui-animation-demo/raw/main/cover.png) 3 | 4 | ## Classical Animation 5 | | Animation | Video Link | 6 | | ------------- | ------------- | 7 | | [Chat Button Animation](https://github.com/HuangRunHua/swiftui-animation-demo/tree/main/ChatButtonAnimation.swiftpm) | [🎬](https://twitter.com/joker_hook/status/1552675172729376768?s=21&t=OoEz9tdYgJ8Usnea8T94Bg) | 8 | | [Submit Button Animation](https://github.com/HuangRunHua/swiftui-animation-demo/tree/main/SubmitButtonAnimation.swiftpm) | [🎬](https://twitter.com/joker_hook/status/1552675172729376768?s=21&t=OoEz9tdYgJ8Usnea8T94Bg) | 9 | | [iMessage Bubble Animation](https://github.com/HuangRunHua/swiftui-animation-demo/tree/main/iMessageBubbleAnimation.swiftpm) | [🎬](https://twitter.com/joker_hook/status/1552970330133524480?s=21&t=OoEz9tdYgJ8Usnea8T94Bg) | 10 | | [MacOS Dock Animation](https://github.com/HuangRunHua/swiftui-macos-dock-animation) | [🎬](https://twitter.com/joker_hook/status/1548996462813511681?s=21&t=OoEz9tdYgJ8Usnea8T94Bg) | 11 | | [Loading Dot Animation](https://github.com/HuangRunHua/swiftui-animation-demo/tree/main/WaitingDotAnimation.swiftpm) | [🎬](https://twitter.com/joker_hook/status/1553252960301289472?s=21&t=hAMq5L_MmaEVyDmz6GqeNA) | 12 | |[Simple Check Animation](https://github.com/HuangRunHua/swiftui-animation-demo/tree/main/DoneButtonAnimation.swiftpm)|[🎬](https://twitter.com/joker_hook/status/1553265987348180992?s=20&t=Azwwdq2516e0-Zk-llCCag)| 13 | | [Loading Animation](https://github.com/HuangRunHua/swiftui-animation-demo/tree/main/LoadingErrorAndSuccessAnimation.swiftpm) | [🎬](https://twitter.com/joker_hook/status/1553951987590254592?s=20&t=Y8B2sKDHLhAe4O9ftO4XCA) | 14 | | [App Store Downloading-like animation](https://github.com/HuangRunHua/EnterButtonAnimation) | [🎬](https://github.com/HuangRunHua/EnterButtonAnimation/blob/master/intro.GIF)| 15 | | [Music Lyrics scrolling animation effect](https://github.com/HuangRunHua/Apple-Music-Lyric-Animation) | [🎬](https://www.iqiyi.com/v_12gaeft27y0.html?social_platform=link&p1=2_22_221&_frd=r3T7FVFZY4WGpn7q8D4%2BAkJ9%2BJ7oBBAvUPij3aANcYjYQqueprX9X30sKyc4b9l0e5P56v1gQOJvo7dG47UKaJgWKFzgweqjN6nCt7WIngk%3D) | 16 | | [Medium Clap Animation](https://github.com/HuangRunHua/swiftui-animation-demo/tree/main/MediumClapAnimation.swiftpm) | [🎬](https://twitter.com/joker_hook/status/1554315608417636352?s=20&t=CxSyJQOzn2U0GW7PO3wqNA) | 17 | | [Twitter Style Like Animation](https://github.com/HuangRunHua/swiftui-animation-demo/tree/main/TwitterLikeAnimation) | [🎬](https://twitter.com/joker_hook/status/1561605727688396800?s=21&t=FV7VsMXXYG5fmBBBbcXvpg) | 18 | | [Dynamic Island Animation](https://github.com/HuangRunHua/swiftui-animation-demo/tree/main/dynamic-island-animation-demo) | [🎬](https://twitter.com/joker_hook/status/1568526249135587328?s=46&t=yuXo-FNwoqUt9uKk8ahUgA) | 19 | | [Slide Window](https://github.com/HuangRunHua/swiftui-animation-demo/tree/main/AnimatedWindows) | [🎬](https://twitter.com/joker_hook/status/1568895128999440384?s=46&t=kRIFkym4jz-KZdVM85uJaQ) | 20 | | [Facebook Tabs Animation](https://github.com/HuangRunHua/swiftui-animation-demo/tree/main/TabLineAnimation) | [🎬](https://twitter.com/joker_hook/status/1569530045865283586?s=46&t=4RU6Rfwa1tAamRj8NwMr3w) | 21 | | [Apple Photo Detail Check Animation](https://github.com/HuangRunHua/swiftui-animation-demo/tree/main/PhotoScaleEffect) | [🎬](https://twitter.com/joker_hook/status/1569974714126450690?s=46&t=jzBO1LcItDUkLe1zHaGiuw) | 22 | -------------------------------------------------------------------------------- /SubmitButtonAnimation.swiftpm/.swiftpm/playgrounds/CachedManifest.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CachedManifest 6 | 7 | manifestData 8 | 9 | eyJkZXBlbmRlbmNpZXMiOltdLCJkaXNwbGF5TmFtZSI6IlN1Ym1pdCBCdXR0 10 | b24gQW5pbWF0aW9uIiwicGFja2FnZUtpbmQiOnsicm9vdCI6e319LCJwbGF0 11 | Zm9ybXMiOlt7Im9wdGlvbnMiOltdLCJwbGF0Zm9ybU5hbWUiOiJpb3MiLCJ2 12 | ZXJzaW9uIjoiMTUuMiJ9XSwicHJvZHVjdHMiOlt7Im5hbWUiOiJTdWJtaXQg 13 | QnV0dG9uIEFuaW1hdGlvbiIsInNldHRpbmdzIjpbeyJkaXNwbGF5VmVyc2lv 14 | biI6WyIxLjAiXX0seyJidW5kbGVWZXJzaW9uIjpbIjEiXX0seyJpT1NBcHBJ 15 | bmZvIjpbeyJhY2NlbnRDb2xvciI6eyJwcmVzZXRDb2xvciI6eyJwcmVzZXRD 16 | b2xvciI6eyJyYXdWYWx1ZSI6InJlZCJ9fX0sImFwcEljb24iOnsicGxhY2Vo 17 | b2xkZXIiOnsiaWNvbiI6eyJyYXdWYWx1ZSI6InNhbmR3aWNoIn19fSwiY2Fw 18 | YWJpbGl0aWVzIjpbXSwic3VwcG9ydGVkRGV2aWNlRmFtaWxpZXMiOlsicGFk 19 | IiwicGhvbmUiXSwic3VwcG9ydGVkSW50ZXJmYWNlT3JpZW50YXRpb25zIjpb 20 | eyJwb3J0cmFpdCI6e319LHsibGFuZHNjYXBlUmlnaHQiOnt9fSx7ImxhbmRz 21 | Y2FwZUxlZnQiOnt9fSx7InBvcnRyYWl0VXBzaWRlRG93biI6eyJjb25kaXRp 22 | b24iOnsiZGV2aWNlRmFtaWxpZXMiOlsicGFkIl19fX1dfV19XSwidGFyZ2V0 23 | cyI6WyJBcHBNb2R1bGUiXSwidHlwZSI6eyJleGVjdXRhYmxlIjpudWxsfX1d 24 | LCJ0YXJnZXRNYXAiOnsiQXBwTW9kdWxlIjp7ImRlcGVuZGVuY2llcyI6W10s 25 | ImV4Y2x1ZGUiOltdLCJuYW1lIjoiQXBwTW9kdWxlIiwicGF0aCI6Ii4iLCJy 26 | ZXNvdXJjZXMiOltdLCJzZXR0aW5ncyI6W10sInR5cGUiOiJleGVjdXRhYmxl 27 | In19LCJ0YXJnZXRzIjpbeyJkZXBlbmRlbmNpZXMiOltdLCJleGNsdWRlIjpb 28 | XSwibmFtZSI6IkFwcE1vZHVsZSIsInBhdGgiOiIuIiwicmVzb3VyY2VzIjpb 29 | XSwic2V0dGluZ3MiOltdLCJ0eXBlIjoiZXhlY3V0YWJsZSJ9XSwidG9vbHNW 30 | ZXJzaW9uIjp7Il92ZXJzaW9uIjoiNS42LjAifX0= 31 | 32 | manifestHash 33 | 34 | vPKZGXDqIkUrJPxbYZDnNcr4rdHTibJ2YQmE9gNKo8Y= 35 | 36 | schemaVersion 37 | 4 38 | swiftPMVersionString 39 | 5.6.0-dev 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /SubmitButtonAnimation.swiftpm/.swiftpm/playgrounds/DocumentThumbnail.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | DocumentThumbnailConfiguration 6 | 7 | accentColorHash 8 | 9 | sfUaUR8doM00i4+FmNsy5hy5Y+X8aeK0FIW/mVkO11o= 10 | 11 | appIconHash 12 | 13 | j1xXD1XdeSHJhh6UG+mKlJKZHRqGLQUoP23a1WyJHMo= 14 | 15 | thumbnailIsPrerendered 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /SubmitButtonAnimation.swiftpm/.swiftpm/playgrounds/DocumentThumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuangRunHua/swiftui-animation-demo/a1b1af789c13ac08c649157170d341770011d54e/SubmitButtonAnimation.swiftpm/.swiftpm/playgrounds/DocumentThumbnail.png -------------------------------------------------------------------------------- /SubmitButtonAnimation.swiftpm/.swiftpm/playgrounds/Workspace.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | AppSettings 6 | 7 | appIconPlaceholderGlyphName 8 | sandwich 9 | appSettingsVersion 10 | 1 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /SubmitButtonAnimation.swiftpm/ContentView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct ContentView: View { 4 | 5 | @State private var submitButtonTapped = false 6 | @State private var animationBegin = false 7 | @State private var submitTextScaledBack = false 8 | @State private var isLoadingComplete = false 9 | @State private var showCheckmark = false 10 | /// When the animation begin disable the button. 11 | @State private var disableButton = false 12 | /// Hide the background stroke view immediately 13 | /// when checkmark appears. 14 | @State private var hideBackgroundStroke = false 15 | @State private var showReloadButton = false 16 | 17 | var body: some View { 18 | VStack { 19 | Button { 20 | /// The background color should turn to fill with indigo color. 21 | /// The duration here is set to 0.3 seconds. 22 | withAnimation(.linear(duration: 0.3)) { 23 | self.submitButtonTapped.toggle() 24 | } 25 | /// When submitButtonTapped is true, the `Submit` text will scale. 26 | /// After 0.2 secs, we need it to scale back. 27 | /// The duration here is set to 0.1 secs. 28 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { 29 | withAnimation(.linear(duration: 0.1)) { 30 | self.submitTextScaledBack.toggle() 31 | } 32 | } 33 | /// After 0.4 secs, the rounded rectangle will turn to a Circle 34 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) { 35 | withAnimation(.linear(duration: 0.2)) { 36 | self.animationBegin.toggle() 37 | } 38 | } 39 | /// After 5 seconds, the loading process is successfully done. 40 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { 41 | withAnimation(.linear(duration: 5)) { 42 | self.isLoadingComplete.toggle() 43 | } 44 | } 45 | /// When the loading process is successfully done 46 | /// show the checkmark view. 47 | /// The duration of checkmark animation is set to 02 secs. 48 | DispatchQueue.main.asyncAfter(deadline: .now() + 5.2) { 49 | self.hideBackgroundStroke.toggle() 50 | withAnimation(.linear(duration: 0.3)) { 51 | self.showCheckmark.toggle() 52 | } 53 | } 54 | /// After the animation ends, show the reload button. 55 | DispatchQueue.main.asyncAfter(deadline: .now() + 5.8) { 56 | self.showReloadButton.toggle() 57 | } 58 | 59 | } label: { 60 | RoundedRectangle(cornerRadius: 40) 61 | .stroke( 62 | self.animationBegin ? Color.gray: Color.lightGreen, 63 | lineWidth: self.hideBackgroundStroke ? 0: self.animationBegin ? 15: self.submitButtonTapped ? 0: 4 64 | ) 65 | .background( 66 | self.showCheckmark ? Color.lightGreen: self.animationBegin ? .clear: self.submitButtonTapped ? Color.lightGreen: Color.clear 67 | ) 68 | .cornerRadius(40) 69 | .frame(width: self.showCheckmark ? 300: self.animationBegin ? 80: 300, height: 80) 70 | .overlay { 71 | /// The animation of this text is shown in the following way: 72 | /// When tap the button, text view will scale and then scale back 73 | /// Finally, it will disappear. 74 | Text("Submit") 75 | .font(.title2) 76 | .foregroundColor(self.submitButtonTapped ? .white: .lightGreen) 77 | .scaleEffect(self.submitTextScaledBack ? 1: self.submitButtonTapped ? 1.2: 1) 78 | .opacity(self.animationBegin ? 0: 1) 79 | 80 | /// This circle is the loading view. 81 | Circle() 82 | .trim(from: 0, to: self.isLoadingComplete ? 1: 0.1) 83 | .rotation(Angle(degrees: -90)) 84 | .stroke(Color.lightGreen, lineWidth: 7) 85 | .frame(width: 72, height: 72) 86 | .opacity(self.showCheckmark ? 0: self.animationBegin ? 1: 0) 87 | 88 | /// Checkmark View. 89 | Checkmark() 90 | .trim(from: 0, to: self.showCheckmark ? 1: 0) 91 | .stroke(Color.white, style: StrokeStyle(lineWidth: 5, lineCap: .round)) 92 | .frame(width: 25, height: 25) 93 | .offset(x: 7, y: 0) 94 | .opacity(self.showCheckmark ? 1: 0) 95 | } 96 | } 97 | .disabled(self.disableButton) 98 | .onChange(of: self.submitButtonTapped) { newValue in 99 | self.disableButton = newValue 100 | } 101 | 102 | Button { 103 | self.submitButtonTapped = false 104 | self.animationBegin = false 105 | self.submitTextScaledBack = false 106 | self.isLoadingComplete = false 107 | self.showCheckmark = false 108 | self.disableButton = false 109 | self.hideBackgroundStroke = false 110 | self.showReloadButton = false 111 | } label: { 112 | Label("Reload", systemImage: "arrow.clockwise") 113 | .foregroundColor(.gray) 114 | } 115 | .padding() 116 | .opacity(self.showReloadButton ? 1: 0) 117 | } 118 | } 119 | } 120 | 121 | 122 | struct Checkmark: Shape { 123 | func path(in rect: CGRect) -> Path { 124 | let width = rect.size.width 125 | let height = rect.size.height 126 | 127 | var path = Path() 128 | path.move(to: .init(x: -0.5 * width, y: 0.5 * height)) 129 | path.addLine(to: .init(x: 0 * width, y: 1.0 * height)) 130 | path.addLine(to: .init(x: 1 * width, y: 0 * height)) 131 | return path 132 | } 133 | } 134 | 135 | extension Color { 136 | static let lightGreen = Color(red: 0.357, green: 0.757, blue: 0.576) 137 | } 138 | 139 | -------------------------------------------------------------------------------- /SubmitButtonAnimation.swiftpm/MyApp.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | @main 4 | struct MyApp: App { 5 | var body: some Scene { 6 | WindowGroup { 7 | ContentView() 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /SubmitButtonAnimation.swiftpm/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.6 2 | 3 | // WARNING: 4 | // This file is automatically generated. 5 | // Do not edit it by hand because the contents will be replaced. 6 | 7 | import PackageDescription 8 | import AppleProductTypes 9 | 10 | let package = Package( 11 | name: "Submit Button Animation", 12 | platforms: [ 13 | .iOS("15.2") 14 | ], 15 | products: [ 16 | .iOSApplication( 17 | name: "Submit Button Animation", 18 | targets: ["AppModule"], 19 | displayVersion: "1.0", 20 | bundleVersion: "1", 21 | appIcon: .placeholder(icon: .sandwich), 22 | accentColor: .presetColor(.red), 23 | supportedDeviceFamilies: [ 24 | .pad, 25 | .phone 26 | ], 27 | supportedInterfaceOrientations: [ 28 | .portrait, 29 | .landscapeRight, 30 | .landscapeLeft, 31 | .portraitUpsideDown(.when(deviceFamilies: [.pad])) 32 | ] 33 | ) 34 | ], 35 | targets: [ 36 | .executableTarget( 37 | name: "AppModule", 38 | path: "." 39 | ) 40 | ] 41 | ) 42 | -------------------------------------------------------------------------------- /TabLineAnimation/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuangRunHua/swiftui-animation-demo/a1b1af789c13ac08c649157170d341770011d54e/TabLineAnimation/.DS_Store -------------------------------------------------------------------------------- /TabLineAnimation/Shared/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /TabLineAnimation/Shared/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "2x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "83.5x83.5" 82 | }, 83 | { 84 | "idiom" : "ios-marketing", 85 | "scale" : "1x", 86 | "size" : "1024x1024" 87 | }, 88 | { 89 | "idiom" : "mac", 90 | "scale" : "1x", 91 | "size" : "16x16" 92 | }, 93 | { 94 | "idiom" : "mac", 95 | "scale" : "2x", 96 | "size" : "16x16" 97 | }, 98 | { 99 | "idiom" : "mac", 100 | "scale" : "1x", 101 | "size" : "32x32" 102 | }, 103 | { 104 | "idiom" : "mac", 105 | "scale" : "2x", 106 | "size" : "32x32" 107 | }, 108 | { 109 | "idiom" : "mac", 110 | "scale" : "1x", 111 | "size" : "128x128" 112 | }, 113 | { 114 | "idiom" : "mac", 115 | "scale" : "2x", 116 | "size" : "128x128" 117 | }, 118 | { 119 | "idiom" : "mac", 120 | "scale" : "1x", 121 | "size" : "256x256" 122 | }, 123 | { 124 | "idiom" : "mac", 125 | "scale" : "2x", 126 | "size" : "256x256" 127 | }, 128 | { 129 | "idiom" : "mac", 130 | "scale" : "1x", 131 | "size" : "512x512" 132 | }, 133 | { 134 | "idiom" : "mac", 135 | "scale" : "2x", 136 | "size" : "512x512" 137 | } 138 | ], 139 | "info" : { 140 | "author" : "xcode", 141 | "version" : 1 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /TabLineAnimation/Shared/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /TabLineAnimation/Shared/BoundsKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BoundsKey.swift 3 | // TabLineAnimation 4 | // 5 | // Created by Runhua Huang on 9/13/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct BoundsKey: PreferenceKey { 11 | static var defaultValue: Anchor? = nil 12 | 13 | static func reduce(value: inout Anchor?, nextValue: () -> Anchor?) { 14 | /// value 选用第一个非nil的值 15 | value = value ?? nextValue() 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /TabLineAnimation/Shared/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // Shared 4 | // 5 | // Created by Runhua Huang on 9/13/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ContentView: View { 11 | 12 | private let tabs: [TabIcon] = [ 13 | TabIcon("Today", image: "doc.text.image"), 14 | TabIcon("Games", image: "tram.fill.tunnel"), 15 | TabIcon("Apps", image: "square.stack.3d.up.fill"), 16 | TabIcon("Search", image: "magnifyingglass") 17 | ] 18 | 19 | @State private var selectedTabIndex = 1 20 | 21 | var body: some View { 22 | VStack { 23 | Spacer() 24 | 25 | HStack(alignment: .bottom) { 26 | ForEach(tabs.indices, id: \.self) { tabIndex in 27 | Spacer() 28 | Button { 29 | withAnimation(.default) { 30 | self.selectedTabIndex = tabIndex 31 | } 32 | } label: { 33 | self.tabs[tabIndex] 34 | } 35 | .foregroundColor(self.selectedTabIndex == tabIndex ? .accentColor: .gray) 36 | .anchorPreference(key: BoundsKey.self, value: .bounds) { anchor in 37 | self.selectedTabIndex == tabIndex ? anchor: nil 38 | } 39 | Spacer() 40 | } 41 | } 42 | .padding([.top]) 43 | .overlayPreferenceValue(BoundsKey.self) { anchor in 44 | GeometryReader { proxy in 45 | Rectangle() 46 | .fill(.gray) 47 | .frame(width: proxy.size.width, height: 0.5, alignment: .topLeading) 48 | } 49 | } 50 | .overlayPreferenceValue(BoundsKey.self) { anchor in 51 | GeometryReader { proxy in 52 | RoundedRectangle(cornerRadius: 5) 53 | .fill(Color.accentColor) 54 | .frame(width: proxy[anchor!].width, height: 5) 55 | .offset(x: proxy[anchor!].minX) 56 | .frame(width: proxy.size.width, height: proxy.size.height, alignment: .topLeading) 57 | } 58 | } 59 | 60 | } 61 | } 62 | } 63 | 64 | struct ContentView_Previews: PreviewProvider { 65 | static var previews: some View { 66 | ContentView() 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /TabLineAnimation/Shared/TabIcon.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TabIcon.swift 3 | // TabLineAnimation 4 | // 5 | // Created by Runhua Huang on 9/13/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct TabIcon: View { 11 | 12 | let image: String 13 | let title: String 14 | 15 | init(_ title: String, image systemName: String) { 16 | self.title = title 17 | self.image = systemName 18 | } 19 | 20 | var body: some View { 21 | VStack { 22 | Image(systemName: image) 23 | .resizable() 24 | .aspectRatio(contentMode: .fit) 25 | .frame(height: 30) 26 | Text(title) 27 | } 28 | } 29 | } 30 | 31 | struct TabIcon_Previews: PreviewProvider { 32 | static var previews: some View { 33 | TabIcon("Today", image: "doc.text.image") 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /TabLineAnimation/Shared/TabLineAnimationApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TabLineAnimationApp.swift 3 | // Shared 4 | // 5 | // Created by Runhua Huang on 9/13/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct TabLineAnimationApp: App { 12 | var body: some Scene { 13 | WindowGroup { 14 | ContentView() 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /TabLineAnimation/TabLineAnimation.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /TabLineAnimation/TabLineAnimation.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /TabLineAnimation/TabLineAnimation.xcodeproj/project.xcworkspace/xcuserdata/runhuahuang.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuangRunHua/swiftui-animation-demo/a1b1af789c13ac08c649157170d341770011d54e/TabLineAnimation/TabLineAnimation.xcodeproj/project.xcworkspace/xcuserdata/runhuahuang.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /TabLineAnimation/TabLineAnimation.xcodeproj/xcuserdata/runhuahuang.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | TabLineAnimation (iOS).xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 1 11 | 12 | TabLineAnimation (macOS).xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 0 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /TabLineAnimation/macOS/macOS.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /TwitterHashmoji.swiftpm/.swiftpm/playgrounds/CachedManifest.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CachedManifest 6 | 7 | manifestData 8 | 9 | eyJkZXBlbmRlbmNpZXMiOltdLCJkaXNwbGF5TmFtZSI6IlR3aXR0ZXJHZW5z 10 | aGluQW5pbWF0aW9uIiwicGFja2FnZUtpbmQiOnsicm9vdCI6e319LCJwbGF0 11 | Zm9ybXMiOlt7Im9wdGlvbnMiOltdLCJwbGF0Zm9ybU5hbWUiOiJpb3MiLCJ2 12 | ZXJzaW9uIjoiMTUuMiJ9XSwicHJvZHVjdHMiOlt7Im5hbWUiOiJUd2l0dGVy 13 | R2Vuc2hpbkFuaW1hdGlvbiIsInNldHRpbmdzIjpbeyJkaXNwbGF5VmVyc2lv 14 | biI6WyIxLjAiXX0seyJidW5kbGVWZXJzaW9uIjpbIjEiXX0seyJpT1NBcHBJ 15 | bmZvIjpbeyJhY2NlbnRDb2xvciI6eyJwcmVzZXRDb2xvciI6eyJwcmVzZXRD 16 | b2xvciI6eyJyYXdWYWx1ZSI6InBpbmsifX19LCJhcHBJY29uIjp7InBsYWNl 17 | aG9sZGVyIjp7Imljb24iOnsicmF3VmFsdWUiOiJoZWFydCJ9fX0sImNhcGFi 18 | aWxpdGllcyI6W10sInN1cHBvcnRlZERldmljZUZhbWlsaWVzIjpbInBhZCIs 19 | InBob25lIl0sInN1cHBvcnRlZEludGVyZmFjZU9yaWVudGF0aW9ucyI6W3si 20 | cG9ydHJhaXQiOnt9fSx7ImxhbmRzY2FwZVJpZ2h0Ijp7fX0seyJsYW5kc2Nh 21 | cGVMZWZ0Ijp7fX0seyJwb3J0cmFpdFVwc2lkZURvd24iOnsiY29uZGl0aW9u 22 | Ijp7ImRldmljZUZhbWlsaWVzIjpbInBhZCJdfX19XX1dfV0sInRhcmdldHMi 23 | OlsiQXBwTW9kdWxlIl0sInR5cGUiOnsiZXhlY3V0YWJsZSI6bnVsbH19XSwi 24 | dGFyZ2V0TWFwIjp7IkFwcE1vZHVsZSI6eyJkZXBlbmRlbmNpZXMiOltdLCJl 25 | eGNsdWRlIjpbXSwibmFtZSI6IkFwcE1vZHVsZSIsInBhdGgiOiIuIiwicmVz 26 | b3VyY2VzIjpbXSwic2V0dGluZ3MiOltdLCJ0eXBlIjoiZXhlY3V0YWJsZSJ9 27 | fSwidGFyZ2V0cyI6W3siZGVwZW5kZW5jaWVzIjpbXSwiZXhjbHVkZSI6W10s 28 | Im5hbWUiOiJBcHBNb2R1bGUiLCJwYXRoIjoiLiIsInJlc291cmNlcyI6W10s 29 | InNldHRpbmdzIjpbXSwidHlwZSI6ImV4ZWN1dGFibGUifV0sInRvb2xzVmVy 30 | c2lvbiI6eyJfdmVyc2lvbiI6IjUuNi4wIn19 31 | 32 | manifestHash 33 | 34 | 1F75KzhVvzIMDsxGtFtjudhfX/L8UJckUiH/ywr/akE= 35 | 36 | schemaVersion 37 | 4 38 | swiftPMVersionString 39 | 5.8.0 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /TwitterHashmoji.swiftpm/.swiftpm/playgrounds/DocumentThumbnail.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | DocumentThumbnailConfiguration 6 | 7 | accentColorHash 8 | 9 | pnpByLx51dqRe1BR8fDT9a60tjuiRrNUapYe96PH2TE= 10 | 11 | appIconHash 12 | 13 | PLloqYIIC+HXpd+Y3ElnOowFLSZC73cwt3U87luHw90= 14 | 15 | thumbnailIsPrerendered 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /TwitterHashmoji.swiftpm/.swiftpm/playgrounds/DocumentThumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuangRunHua/swiftui-animation-demo/a1b1af789c13ac08c649157170d341770011d54e/TwitterHashmoji.swiftpm/.swiftpm/playgrounds/DocumentThumbnail.png -------------------------------------------------------------------------------- /TwitterHashmoji.swiftpm/.swiftpm/playgrounds/Workspace.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | AppSettings 6 | 7 | appIconPlaceholderGlyphName 8 | heart 9 | appSettingsVersion 10 | 1 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /TwitterHashmoji.swiftpm/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /TwitterHashmoji.swiftpm/.swiftpm/xcode/package.xcworkspace/xcuserdata/runhuahuang.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuangRunHua/swiftui-animation-demo/a1b1af789c13ac08c649157170d341770011d54e/TwitterHashmoji.swiftpm/.swiftpm/xcode/package.xcworkspace/xcuserdata/runhuahuang.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /TwitterHashmoji.swiftpm/.swiftpm/xcode/xcuserdata/runhuahuang.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | TwitterGenshinAnimation.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /TwitterHashmoji.swiftpm/AnimationList.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AnimationList.swift 3 | // TwitterGenshinAnimation 4 | // 5 | // Created by Runhua Huang on 2025/4/15. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct AnimationImage: Identifiable { 11 | let id: UUID = UUID() 12 | let image: String 13 | 14 | static let animationImages: [AnimationImage] = [ 15 | AnimationImage(image: "doge"), 16 | AnimationImage(image: "emoji2"), 17 | AnimationImage(image: "doge2"), 18 | ] 19 | } 20 | 21 | struct AnimationList: View { 22 | 23 | var body: some View { 24 | NavigationView { 25 | ScrollView { 26 | VStack(spacing: 10) { 27 | ForEach(AnimationImage.animationImages) { image in 28 | TwitterAnimationButton(image: image.image) 29 | .frame(width: 2000, height: 200) 30 | } 31 | } 32 | } 33 | .navigationTitle("星期二") 34 | .navigationBarTitleDisplayMode(.large) 35 | } 36 | } 37 | } 38 | 39 | #Preview { 40 | AnimationList() 41 | } 42 | -------------------------------------------------------------------------------- /TwitterHashmoji.swiftpm/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /TwitterHashmoji.swiftpm/Assets.xcassets/doge.imageset/ABUIABAEGAAg8dnTnQYooqv96QQwpwQ4_AE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuangRunHua/swiftui-animation-demo/a1b1af789c13ac08c649157170d341770011d54e/TwitterHashmoji.swiftpm/Assets.xcassets/doge.imageset/ABUIABAEGAAg8dnTnQYooqv96QQwpwQ4_AE.png -------------------------------------------------------------------------------- /TwitterHashmoji.swiftpm/Assets.xcassets/doge.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "ABUIABAEGAAg8dnTnQYooqv96QQwpwQ4_AE.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /TwitterHashmoji.swiftpm/Assets.xcassets/doge2.imageset/BbsImg_62400881131778_1606876706_s_54187_o_w_300_h_300_45775.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuangRunHua/swiftui-animation-demo/a1b1af789c13ac08c649157170d341770011d54e/TwitterHashmoji.swiftpm/Assets.xcassets/doge2.imageset/BbsImg_62400881131778_1606876706_s_54187_o_w_300_h_300_45775.png -------------------------------------------------------------------------------- /TwitterHashmoji.swiftpm/Assets.xcassets/doge2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "BbsImg_62400881131778_1606876706_s_54187_o_w_300_h_300_45775.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /TwitterHashmoji.swiftpm/Assets.xcassets/emoji2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "PNG image.png", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /TwitterHashmoji.swiftpm/Assets.xcassets/emoji2.imageset/PNG image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuangRunHua/swiftui-animation-demo/a1b1af789c13ac08c649157170d341770011d54e/TwitterHashmoji.swiftpm/Assets.xcassets/emoji2.imageset/PNG image.png -------------------------------------------------------------------------------- /TwitterHashmoji.swiftpm/ColorExtension.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | extension Color { 4 | static let darkPink: Color = Color(red: 0.808, green: 0.255, blue: 0.373) 5 | } 6 | -------------------------------------------------------------------------------- /TwitterHashmoji.swiftpm/MyApp.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | @main 4 | struct MyApp: App { 5 | var body: some Scene { 6 | WindowGroup { 7 | AnimationList() 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /TwitterHashmoji.swiftpm/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.6 2 | 3 | // WARNING: 4 | // This file is automatically generated. 5 | // Do not edit it by hand because the contents will be replaced. 6 | 7 | import PackageDescription 8 | import AppleProductTypes 9 | 10 | let package = Package( 11 | name: "TwitterGenshinAnimation", 12 | platforms: [ 13 | .iOS("15.2") 14 | ], 15 | products: [ 16 | .iOSApplication( 17 | name: "TwitterGenshinAnimation", 18 | targets: ["AppModule"], 19 | teamIdentifier: "YRB62S584T", 20 | displayVersion: "1.0", 21 | bundleVersion: "1", 22 | appIcon: .placeholder(icon: .heart), 23 | accentColor: .presetColor(.pink), 24 | supportedDeviceFamilies: [ 25 | .pad, 26 | .phone 27 | ], 28 | supportedInterfaceOrientations: [ 29 | .portrait, 30 | .landscapeRight, 31 | .landscapeLeft, 32 | .portraitUpsideDown(.when(deviceFamilies: [.pad])) 33 | ] 34 | ) 35 | ], 36 | targets: [ 37 | .executableTarget( 38 | name: "AppModule", 39 | path: "." 40 | ) 41 | ] 42 | ) -------------------------------------------------------------------------------- /TwitterHashmoji.swiftpm/TwitterAnimationButton.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct TwitterAnimationButton: View { 4 | let image: String 5 | typealias Seconds = CGFloat 6 | /// 设定Image的缩放规则 7 | private enum ImageScaleRate: CGFloat { 8 | case original = 0.6 9 | case large = 1.4 10 | case small = 0.1 11 | } 12 | /// 设定heart的缩放规则 13 | private enum HeartScaleRate: CGFloat { 14 | case normal = 1.0 15 | case small = 0.1 16 | case large = 1.5 17 | } 18 | /// 用于判断当前heart的状态与处理点击heart触发的事件 19 | @State private var isLike: Bool = false 20 | /// 初始状态下Image的大小 21 | @State private var imageScaleRate: ImageScaleRate = .original 22 | /// 初始状态下heart的大小 23 | @State private var heartScaleRate: HeartScaleRate = .normal 24 | /// Red heart应当在Image消失后出现,此时为true 25 | @State private var showReadHeart: Bool = false 26 | /// 由于heart会分两次出现,一次是刚刚开始的时候以没有填充的姿态出现 27 | /// 一次是在Image消失之后以红色的状态出现 28 | @State private var bottonTapped: Bool = false 29 | /// 初始状态下与Image缩小之后隐藏Image使用 30 | @State private var hideImage: Bool = true 31 | 32 | /// 点击heart后的动画持续时间,包括heart突然消失与图片的突然出现 33 | private let tapReactionTimer: Seconds = 0.01 34 | /// 图片放大持续时间 35 | private let imageEnlargeTimer: Seconds = 0.25 36 | /// 图片缩小持续时间 37 | private let imageZoomOutTimer: Seconds = 0.25 38 | /// 图片开始缩小的时刻 39 | private var imageZoomOutStartingTime: Seconds { 40 | /// 在这里让图片放大后保持 0.15 秒钟的时间 41 | return imageEnlargeTimer + 0.15 42 | } 43 | /// heart开始出现并准备放大的时间 44 | private var heartZoomInStartingTime: Seconds { 45 | return imageZoomOutStartingTime + imageZoomOutTimer 46 | } 47 | /// heart放大持续的时间 48 | private let heartZoomInTimer: Seconds = 0.1 49 | /// dismiss阶段heart放大与缩小持续时间 50 | private let dismissHeartZoomInTimer: Seconds = 0.15 51 | /// dismiss阶段heart放大后缩小的开始时间 52 | private var dismissHeartZoomOutStartingTime: Seconds { 53 | return dismissHeartZoomInTimer + 0.1 54 | } 55 | 56 | var body: some View { 57 | ZStack { 58 | Image(image) 59 | .resizable() 60 | .aspectRatio(contentMode: .fit) 61 | .scaleEffect(imageScaleRate.rawValue) 62 | .offset(x: 0, y: -10) 63 | .opacity(self.hideImage ? 0 : 1) 64 | 65 | Button(action: { 66 | isLike ? unlike() : like() 67 | }, label: { 68 | Image(systemName: self.isLike ? "heart.fill" : "heart") 69 | .resizable() 70 | .aspectRatio(contentMode: .fit) 71 | .foregroundColor(self.isLike ? .darkPink : .gray) 72 | .opacity(self.showReadHeart ? 1: self.bottonTapped ? 0 : 1) 73 | .scaleEffect(heartScaleRate.rawValue) 74 | 75 | }) 76 | } 77 | .frame(width: 100, height: 100, alignment: .center) 78 | } 79 | } 80 | 81 | 82 | extension TwitterAnimationButton { 83 | private func like() { 84 | withAnimation(.easeInOut(duration: tapReactionTimer)) { 85 | self.bottonTapped.toggle() 86 | self.isLike.toggle() 87 | self.hideImage.toggle() 88 | self.heartScaleRate = .small 89 | } 90 | withAnimation(.easeInOut(duration: imageEnlargeTimer)) { 91 | self.imageScaleRate = .large 92 | } 93 | DispatchQueue.main.asyncAfter(deadline: .now() + imageZoomOutStartingTime) { 94 | withAnimation(.easeInOut(duration: imageZoomOutTimer)) { 95 | self.imageScaleRate = .small 96 | } 97 | } 98 | DispatchQueue.main.asyncAfter(deadline: .now() + heartZoomInStartingTime) { 99 | self.hideImage = true 100 | self.showReadHeart = true 101 | withAnimation(.easeInOut(duration: heartZoomInTimer)) { 102 | self.heartScaleRate = .normal 103 | } 104 | } 105 | } 106 | private func unlike() { 107 | self.imageScaleRate = .original 108 | self.bottonTapped = false 109 | self.isLike = false 110 | self.hideImage = true 111 | self.showReadHeart = false 112 | withAnimation(.easeInOut(duration: dismissHeartZoomInTimer)) { 113 | self.heartScaleRate = .large 114 | } 115 | DispatchQueue.main.asyncAfter(deadline: .now() + dismissHeartZoomOutStartingTime) { 116 | withAnimation(.easeInOut(duration: dismissHeartZoomInTimer)) { 117 | self.heartScaleRate = .normal 118 | } 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /TwitterLikeAnimation/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuangRunHua/swiftui-animation-demo/a1b1af789c13ac08c649157170d341770011d54e/TwitterLikeAnimation/.DS_Store -------------------------------------------------------------------------------- /TwitterLikeAnimation/TwitterLikeAnimation.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /TwitterLikeAnimation/TwitterLikeAnimation.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /TwitterLikeAnimation/TwitterLikeAnimation.xcodeproj/project.xcworkspace/xcuserdata/runhuahuang.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuangRunHua/swiftui-animation-demo/a1b1af789c13ac08c649157170d341770011d54e/TwitterLikeAnimation/TwitterLikeAnimation.xcodeproj/project.xcworkspace/xcuserdata/runhuahuang.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /TwitterLikeAnimation/TwitterLikeAnimation.xcodeproj/xcuserdata/runhuahuang.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | TwitterLikeAnimation.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /TwitterLikeAnimation/TwitterLikeAnimation/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuangRunHua/swiftui-animation-demo/a1b1af789c13ac08c649157170d341770011d54e/TwitterLikeAnimation/TwitterLikeAnimation/.DS_Store -------------------------------------------------------------------------------- /TwitterLikeAnimation/TwitterLikeAnimation/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /TwitterLikeAnimation/TwitterLikeAnimation/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /TwitterLikeAnimation/TwitterLikeAnimation/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /TwitterLikeAnimation/TwitterLikeAnimation/Assets.xcassets/darkPink.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "display-p3", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.373", 9 | "green" : "0.255", 10 | "red" : "0.808" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /TwitterLikeAnimation/TwitterLikeAnimation/Assets.xcassets/lightBlue.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "display-p3", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.961", 9 | "green" : "0.812", 10 | "red" : "0.627" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /TwitterLikeAnimation/TwitterLikeAnimation/Assets.xcassets/lightGreen.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "display-p3", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.769", 9 | "green" : "0.894", 10 | "red" : "0.627" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /TwitterLikeAnimation/TwitterLikeAnimation/Assets.xcassets/lightRed.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "display-p3", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.655", 9 | "green" : "0.576", 10 | "red" : "0.898" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /TwitterLikeAnimation/TwitterLikeAnimation/BinaryCircle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BinaryCircle.swift 3 | // TwitterLikeAnimation 4 | // 5 | // Created by Runhua Huang on 2022/8/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct BinaryCircle: View { 11 | @Binding var spacing: CGFloat 12 | @Binding var rotateAngle: CGFloat 13 | @Binding var scaleRate: CGFloat 14 | 15 | var body: some View { 16 | HStack(spacing: spacing) { 17 | Circle() 18 | .scaleEffect(1/self.scaleRate) 19 | Circle() 20 | .scaleEffect(self.scaleRate) 21 | } 22 | .rotationEffect(.degrees(rotateAngle), anchor: .center) 23 | } 24 | } 25 | 26 | struct BinaryCircle_Previews: PreviewProvider { 27 | static var previews: some View { 28 | BinaryCircle(spacing: .constant(0), rotateAngle: .constant(45), scaleRate: .constant(1)) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /TwitterLikeAnimation/TwitterLikeAnimation/ColorExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ColorExtension.swift 3 | // TwitterLikeAnimation 4 | // 5 | // Created by Runhua Huang on 2022/8/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | extension Color { 11 | static let darkPink = Color("darkPink") 12 | static let lightGreen = Color("lightGreen") 13 | static let lightBlue = Color("lightBlue") 14 | static let lightRed = Color("lightRed") 15 | } 16 | -------------------------------------------------------------------------------- /TwitterLikeAnimation/TwitterLikeAnimation/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // TwitterLikeAnimation 4 | // 5 | // Created by Runhua Huang on 2022/8/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ContentView: View { 11 | 12 | @State private var buttonTapped: Bool = false 13 | /// For heart with red color. 14 | @State private var showRedHeart: Bool = false 15 | @State private var redHeartScale: CGFloat = 0.2 16 | 17 | @State private var hideTwoCircles: Bool = true 18 | @State private var colorCircleScaleRate: CGFloat = 0.2 19 | @State private var colorCircleColor: Color = .darkPink 20 | @State private var whiteCircleScaleRate: CGFloat = 0.01 21 | /// For the binary circle. 22 | @State private var binaryCircleSpacing: CGFloat = 0.0 23 | @State private var binaryCircleRotateAngle: CGFloat = 45.0 24 | @State private var binaryCircleScaleRate: CGFloat = 1.0 25 | @State private var binaryCircleOffset: CGFloat = -58 26 | @State private var showBinaryCircleRing: Bool = false 27 | 28 | @State private var isLike: Bool = false 29 | 30 | @Environment(\.colorScheme) var colorScheme 31 | 32 | var body: some View { 33 | ZStack { 34 | Circle() 35 | .foregroundColor(colorCircleColor) 36 | .scaleEffect(colorCircleScaleRate) 37 | .frame(width: 100, height: 100) 38 | .opacity(self.hideTwoCircles ? 0: 1) 39 | 40 | Circle() 41 | .foregroundColor(colorScheme == .light ? .white: .black) 42 | .scaleEffect(whiteCircleScaleRate) 43 | .frame(width: 100, height: 100) 44 | .opacity(self.hideTwoCircles ? 0: 1) 45 | 46 | binaryCircleRing 47 | .opacity(self.showBinaryCircleRing ? 1: 0) 48 | 49 | Button { 50 | self.isLike ? restoreButton(): like() 51 | } label: { 52 | Image(systemName: self.isLike ? "heart.fill": "heart") 53 | .resizable() 54 | .aspectRatio(contentMode: .fit) 55 | .foregroundColor(self.showRedHeart ? .darkPink: .gray) 56 | .frame(width: 100, height: 100) 57 | .scaleEffect(self.buttonTapped ? self.redHeartScale: 1) 58 | } 59 | .buttonStyle(.plain) 60 | .opacity(self.showRedHeart ? 1: self.buttonTapped ? 0: 1) 61 | } 62 | } 63 | } 64 | 65 | struct ContentView_Previews: PreviewProvider { 66 | static var previews: some View { 67 | ContentView() 68 | } 69 | } 70 | 71 | extension ContentView { 72 | private var binaryCircleRing: some View { 73 | ZStack { 74 | BinaryCircle(spacing: $binaryCircleSpacing, rotateAngle: $binaryCircleRotateAngle, scaleRate: $binaryCircleScaleRate) 75 | .offset(y: self.binaryCircleOffset) 76 | .frame(width: 30, height: 20) 77 | .rotationEffect(.degrees(0), anchor: .center) 78 | .foregroundColor(.lightBlue) 79 | 80 | BinaryCircle(spacing: $binaryCircleSpacing, rotateAngle: $binaryCircleRotateAngle, scaleRate: $binaryCircleScaleRate) 81 | .offset(y: self.binaryCircleOffset) 82 | .frame(width: 30, height: 20) 83 | .rotationEffect(.degrees(360/7), anchor: .center) 84 | .foregroundColor(.purple) 85 | 86 | BinaryCircle(spacing: $binaryCircleSpacing, rotateAngle: $binaryCircleRotateAngle, scaleRate: $binaryCircleScaleRate) 87 | .offset(y: self.binaryCircleOffset) 88 | .frame(width: 30, height: 20) 89 | .rotationEffect(.degrees(360*2/7), anchor: .center) 90 | .foregroundColor(.lightGreen) 91 | 92 | BinaryCircle(spacing: $binaryCircleSpacing, rotateAngle: $binaryCircleRotateAngle, scaleRate: $binaryCircleScaleRate) 93 | .offset(y: self.binaryCircleOffset) 94 | .frame(width: 30, height: 20) 95 | .rotationEffect(.degrees(360*3/7), anchor: .center) 96 | .foregroundColor(.lightRed) 97 | 98 | BinaryCircle(spacing: $binaryCircleSpacing, rotateAngle: $binaryCircleRotateAngle, scaleRate: $binaryCircleScaleRate) 99 | .offset(y: self.binaryCircleOffset) 100 | .frame(width: 30, height: 20) 101 | .rotationEffect(.degrees(360*4/7), anchor: .center) 102 | .foregroundColor(.lightBlue) 103 | 104 | BinaryCircle(spacing: $binaryCircleSpacing, rotateAngle: $binaryCircleRotateAngle, scaleRate: $binaryCircleScaleRate) 105 | .offset(y: self.binaryCircleOffset) 106 | .frame(width: 30, height: 20) 107 | .rotationEffect(.degrees(360*5/7), anchor: .center) 108 | .foregroundColor(.purple) 109 | 110 | BinaryCircle(spacing: $binaryCircleSpacing, rotateAngle: $binaryCircleRotateAngle, scaleRate: $binaryCircleScaleRate) 111 | .offset(y: self.binaryCircleOffset) 112 | .frame(width: 30, height: 20) 113 | .rotationEffect(.degrees(360*6/7), anchor: .center) 114 | .foregroundColor(.lightGreen) 115 | } 116 | } 117 | } 118 | 119 | 120 | extension ContentView { 121 | private func like() { 122 | withAnimation(.easeInOut(duration: 0.01)) { 123 | self.buttonTapped.toggle() 124 | self.isLike.toggle() 125 | self.hideTwoCircles.toggle() 126 | } 127 | withAnimation(.easeInOut(duration: 0.2)) { 128 | self.colorCircleScaleRate = 1.2 129 | } 130 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { 131 | withAnimation(.easeInOut(duration: 0.3)) { 132 | self.colorCircleColor = .purple 133 | self.whiteCircleScaleRate = 1.13 134 | } 135 | } 136 | 137 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) { 138 | withAnimation(.easeInOut(duration: 0.1)) { 139 | self.showBinaryCircleRing.toggle() 140 | self.hideTwoCircles.toggle() 141 | self.showRedHeart.toggle() 142 | } 143 | } 144 | 145 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { 146 | withAnimation(.easeInOut(duration: 0.5)) { 147 | self.binaryCircleRotateAngle = -45 148 | self.binaryCircleOffset = -100 149 | self.binaryCircleScaleRate = 1.2 150 | self.binaryCircleSpacing = 10 151 | } 152 | 153 | } 154 | 155 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) { 156 | withAnimation(.spring(response: 0.5, dampingFraction: 0.5, blendDuration: 0.1)) { 157 | self.redHeartScale = 1.0 158 | } 159 | } 160 | 161 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.8) { 162 | withAnimation(.easeInOut(duration: 0.2)) { 163 | self.showBinaryCircleRing.toggle() 164 | } 165 | } 166 | } 167 | 168 | private func restoreButton() { 169 | self.buttonTapped = false 170 | self.showRedHeart = false 171 | self.redHeartScale = 0.2 172 | self.colorCircleScaleRate = 0.2 173 | self.colorCircleColor = .darkPink 174 | self.whiteCircleScaleRate = 0.01 175 | self.binaryCircleSpacing = 0.0 176 | self.binaryCircleRotateAngle = 45.0 177 | self.binaryCircleScaleRate = 1.0 178 | self.binaryCircleOffset = -58 179 | self.showBinaryCircleRing = false 180 | self.hideTwoCircles = true 181 | self.isLike = false 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /TwitterLikeAnimation/TwitterLikeAnimation/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /TwitterLikeAnimation/TwitterLikeAnimation/TwitterLikeAnimationApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TwitterLikeAnimationApp.swift 3 | // TwitterLikeAnimation 4 | // 5 | // Created by Runhua Huang on 2022/8/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct TwitterLikeAnimationApp: App { 12 | var body: some Scene { 13 | WindowGroup { 14 | ContentView() 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /WaitingDotAnimation.swiftpm/.swiftpm/playgrounds/CachedManifest.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CachedManifest 6 | 7 | manifestData 8 | 9 | eyJkZXBlbmRlbmNpZXMiOltdLCJkaXNwbGF5TmFtZSI6IldhaW50aW5nIERv 10 | dCBBbmltYXRpb24iLCJwYWNrYWdlS2luZCI6eyJyb290Ijp7fX0sInBsYXRm 11 | b3JtcyI6W3sib3B0aW9ucyI6W10sInBsYXRmb3JtTmFtZSI6ImlvcyIsInZl 12 | cnNpb24iOiIxNS4yIn1dLCJwcm9kdWN0cyI6W3sibmFtZSI6IldhaW50aW5n 13 | IERvdCBBbmltYXRpb24iLCJzZXR0aW5ncyI6W3siZGlzcGxheVZlcnNpb24i 14 | OlsiMS4wIl19LHsiYnVuZGxlVmVyc2lvbiI6WyIxIl19LHsiaU9TQXBwSW5m 15 | byI6W3siYWNjZW50Q29sb3IiOnsicHJlc2V0Q29sb3IiOnsicHJlc2V0Q29s 16 | b3IiOnsicmF3VmFsdWUiOiJ5ZWxsb3cifX19LCJhcHBJY29uIjp7InBsYWNl 17 | aG9sZGVyIjp7Imljb24iOnsicmF3VmFsdWUiOiJjbG9jayJ9fX0sImNhcGFi 18 | aWxpdGllcyI6W10sInN1cHBvcnRlZERldmljZUZhbWlsaWVzIjpbInBhZCIs 19 | InBob25lIl0sInN1cHBvcnRlZEludGVyZmFjZU9yaWVudGF0aW9ucyI6W3si 20 | cG9ydHJhaXQiOnt9fSx7ImxhbmRzY2FwZVJpZ2h0Ijp7fX0seyJsYW5kc2Nh 21 | cGVMZWZ0Ijp7fX0seyJwb3J0cmFpdFVwc2lkZURvd24iOnsiY29uZGl0aW9u 22 | Ijp7ImRldmljZUZhbWlsaWVzIjpbInBhZCJdfX19XX1dfV0sInRhcmdldHMi 23 | OlsiQXBwTW9kdWxlIl0sInR5cGUiOnsiZXhlY3V0YWJsZSI6bnVsbH19XSwi 24 | dGFyZ2V0TWFwIjp7IkFwcE1vZHVsZSI6eyJkZXBlbmRlbmNpZXMiOltdLCJl 25 | eGNsdWRlIjpbXSwibmFtZSI6IkFwcE1vZHVsZSIsInBhdGgiOiIuIiwicmVz 26 | b3VyY2VzIjpbXSwic2V0dGluZ3MiOltdLCJ0eXBlIjoiZXhlY3V0YWJsZSJ9 27 | fSwidGFyZ2V0cyI6W3siZGVwZW5kZW5jaWVzIjpbXSwiZXhjbHVkZSI6W10s 28 | Im5hbWUiOiJBcHBNb2R1bGUiLCJwYXRoIjoiLiIsInJlc291cmNlcyI6W10s 29 | InNldHRpbmdzIjpbXSwidHlwZSI6ImV4ZWN1dGFibGUifV0sInRvb2xzVmVy 30 | c2lvbiI6eyJfdmVyc2lvbiI6IjUuNi4wIn19 31 | 32 | manifestHash 33 | 34 | LZsjq2G6byjxlYFcU6eEZX6w/5dZAnNnFEi6+VC6jVk= 35 | 36 | schemaVersion 37 | 4 38 | swiftPMVersionString 39 | 5.6.0-dev 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /WaitingDotAnimation.swiftpm/.swiftpm/playgrounds/DocumentThumbnail.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | DocumentThumbnailConfiguration 6 | 7 | accentColorHash 8 | 9 | xoWiybqyNczdKrDqkigaUhyKrzeJVJPQgAcOoA/H9dc= 10 | 11 | appIconHash 12 | 13 | 2BmO+jYE0WSFNGhgjFXvoUi8VuNWTVowIyv5i4q0Ous= 14 | 15 | thumbnailIsPrerendered 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /WaitingDotAnimation.swiftpm/.swiftpm/playgrounds/DocumentThumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuangRunHua/swiftui-animation-demo/a1b1af789c13ac08c649157170d341770011d54e/WaitingDotAnimation.swiftpm/.swiftpm/playgrounds/DocumentThumbnail.png -------------------------------------------------------------------------------- /WaitingDotAnimation.swiftpm/.swiftpm/playgrounds/Workspace.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | AppSettings 6 | 7 | appIconPlaceholderGlyphName 8 | clock 9 | appSettingsVersion 10 | 1 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /WaitingDotAnimation.swiftpm/ContentView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct ContentView: View { 4 | 5 | @State private var currentColorIndex = 0 6 | @State private var scaleRate = 0.1 7 | 8 | private let colors: [Color] = [ 9 | .purple, .brown, .green, .cyan, .mint, .orange, .pink 10 | ] 11 | 12 | private let timer = Timer.publish(every: 2, on: .main, in: .common).autoconnect() 13 | 14 | private var repeatingAnimation: Animation { 15 | Animation 16 | .easeInOut(duration: 1) 17 | .repeatForever() 18 | } 19 | 20 | var body: some View { 21 | Circle() 22 | .foregroundColor(colors[currentColorIndex]) 23 | .frame(width: 20, height: 20) 24 | .scaleEffect(scaleRate) 25 | .onAppear { 26 | withAnimation(self.repeatingAnimation) { 27 | self.scaleRate = 3 28 | } 29 | } 30 | .onReceive(timer) { _ in 31 | self.currentColorIndex = (self.currentColorIndex + 1) >= self.colors.count ? 0: self.currentColorIndex + 1 32 | } 33 | } 34 | } 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /WaitingDotAnimation.swiftpm/MyApp.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | @main 4 | struct MyApp: App { 5 | var body: some Scene { 6 | WindowGroup { 7 | ContentView() 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /WaitingDotAnimation.swiftpm/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.6 2 | 3 | // WARNING: 4 | // This file is automatically generated. 5 | // Do not edit it by hand because the contents will be replaced. 6 | 7 | import PackageDescription 8 | import AppleProductTypes 9 | 10 | let package = Package( 11 | name: "Wainting Dot Animation", 12 | platforms: [ 13 | .iOS("15.2") 14 | ], 15 | products: [ 16 | .iOSApplication( 17 | name: "Wainting Dot Animation", 18 | targets: ["AppModule"], 19 | displayVersion: "1.0", 20 | bundleVersion: "1", 21 | appIcon: .placeholder(icon: .clock), 22 | accentColor: .presetColor(.yellow), 23 | supportedDeviceFamilies: [ 24 | .pad, 25 | .phone 26 | ], 27 | supportedInterfaceOrientations: [ 28 | .portrait, 29 | .landscapeRight, 30 | .landscapeLeft, 31 | .portraitUpsideDown(.when(deviceFamilies: [.pad])) 32 | ] 33 | ) 34 | ], 35 | targets: [ 36 | .executableTarget( 37 | name: "AppModule", 38 | path: "." 39 | ) 40 | ] 41 | ) 42 | -------------------------------------------------------------------------------- /cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuangRunHua/swiftui-animation-demo/a1b1af789c13ac08c649157170d341770011d54e/cover.png -------------------------------------------------------------------------------- /dynamic-island-animation-demo/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuangRunHua/swiftui-animation-demo/a1b1af789c13ac08c649157170d341770011d54e/dynamic-island-animation-demo/.DS_Store -------------------------------------------------------------------------------- /dynamic-island-animation-demo/Shared/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /dynamic-island-animation-demo/Shared/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | }, 93 | { 94 | "idiom" : "mac", 95 | "scale" : "1x", 96 | "size" : "16x16" 97 | }, 98 | { 99 | "idiom" : "mac", 100 | "scale" : "2x", 101 | "size" : "16x16" 102 | }, 103 | { 104 | "idiom" : "mac", 105 | "scale" : "1x", 106 | "size" : "32x32" 107 | }, 108 | { 109 | "idiom" : "mac", 110 | "scale" : "2x", 111 | "size" : "32x32" 112 | }, 113 | { 114 | "idiom" : "mac", 115 | "scale" : "1x", 116 | "size" : "128x128" 117 | }, 118 | { 119 | "idiom" : "mac", 120 | "scale" : "2x", 121 | "size" : "128x128" 122 | }, 123 | { 124 | "idiom" : "mac", 125 | "scale" : "1x", 126 | "size" : "256x256" 127 | }, 128 | { 129 | "idiom" : "mac", 130 | "scale" : "2x", 131 | "size" : "256x256" 132 | }, 133 | { 134 | "idiom" : "mac", 135 | "scale" : "1x", 136 | "size" : "512x512" 137 | }, 138 | { 139 | "idiom" : "mac", 140 | "scale" : "2x", 141 | "size" : "512x512" 142 | } 143 | ], 144 | "info" : { 145 | "author" : "xcode", 146 | "version" : 1 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /dynamic-island-animation-demo/Shared/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /dynamic-island-animation-demo/Shared/Assets.xcassets/back.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "linus-nylund-JP23z_-dA74-unsplash.jpg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /dynamic-island-animation-demo/Shared/Assets.xcassets/back.imageset/linus-nylund-JP23z_-dA74-unsplash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuangRunHua/swiftui-animation-demo/a1b1af789c13ac08c649157170d341770011d54e/dynamic-island-animation-demo/Shared/Assets.xcassets/back.imageset/linus-nylund-JP23z_-dA74-unsplash.jpg -------------------------------------------------------------------------------- /dynamic-island-animation-demo/Shared/Assets.xcassets/music.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "music.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /dynamic-island-animation-demo/Shared/Assets.xcassets/music.imageset/music.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuangRunHua/swiftui-animation-demo/a1b1af789c13ac08c649157170d341770011d54e/dynamic-island-animation-demo/Shared/Assets.xcassets/music.imageset/music.png -------------------------------------------------------------------------------- /dynamic-island-animation-demo/Shared/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // Shared 4 | // 5 | // Created by Runhua Huang on 2022/9/9. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ContentView: View { 11 | @Namespace private var shapeTransition 12 | @State private var currentIsland: IslandMode = .none 13 | @State private var showBatteryInfo: Bool = false 14 | @State private var showMusicInfo:Bool = false 15 | @State private var showMusicdetailInfo: Bool = false 16 | @State private var showMusicScaleDetailInfo: Bool = false 17 | @State private var buttonDisabled: Bool = false 18 | 19 | var body: some View { 20 | ZStack { 21 | VStack { 22 | if currentIsland == .none { 23 | BackgroundView() 24 | .matchedGeometryEffect(id: "island", in: shapeTransition) 25 | .overlay { 26 | ZStack { 27 | DynamicIslandBatteryView() 28 | .opacity(0) 29 | .matchedGeometryEffect(id: "batteryview", in: shapeTransition) 30 | MusicView(showScaleView: $showMusicdetailInfo, showMusicdetialInfo: $showMusicScaleDetailInfo) 31 | .opacity(0) 32 | .offset(x: 0, y: self.showMusicScaleDetailInfo ? -50: 0) 33 | .matchedGeometryEffect(id: "musicview", in: shapeTransition) 34 | } 35 | } 36 | .frame(width: 140, height: 40) 37 | } else if currentIsland == .batteryView { 38 | BackgroundView(cornerRadius: 30) 39 | .matchedGeometryEffect(id: "island", in: shapeTransition) 40 | .overlay { 41 | ZStack { 42 | DynamicIslandBatteryView() 43 | .opacity(self.showBatteryInfo ? 1: 0) 44 | .matchedGeometryEffect(id: "batteryview", in: shapeTransition) 45 | MusicView(showScaleView: $showMusicdetailInfo, showMusicdetialInfo: $showMusicScaleDetailInfo) 46 | .opacity(0) 47 | .offset(x: 0, y: self.showMusicScaleDetailInfo ? -50: 0) 48 | .matchedGeometryEffect(id: "musicview", in: shapeTransition) 49 | 50 | } 51 | } 52 | .frame(width: 350, height: 50) 53 | } else if currentIsland == .music { 54 | BackgroundView() 55 | .matchedGeometryEffect(id: "island", in: shapeTransition) 56 | .shadow(color: .gray, radius: self.showMusicdetailInfo ? 10: 0, y: self.showMusicdetailInfo ? 10: 0) 57 | .overlay { 58 | ZStack { 59 | DynamicIslandBatteryView() 60 | .opacity(0) 61 | .matchedGeometryEffect(id: "batteryview", in: shapeTransition) 62 | MusicView(showScaleView: $showMusicdetailInfo, showMusicdetialInfo: $showMusicScaleDetailInfo) 63 | .opacity(self.showMusicInfo ? 1: 0) 64 | .offset(x: 0, y: self.showMusicScaleDetailInfo ? -50: 0) 65 | .matchedGeometryEffect(id: "musicview", in: shapeTransition) 66 | } 67 | } 68 | .frame(width: self.showMusicScaleDetailInfo ? 360: self.showMusicdetailInfo ? 320: 300, height: self.showMusicScaleDetailInfo ? 210: self.showMusicdetailInfo ? 90: 60) 69 | } 70 | Spacer() 71 | } 72 | self.buttons 73 | } 74 | 75 | } 76 | } 77 | 78 | struct ContentView_Previews: PreviewProvider { 79 | static var previews: some View { 80 | ContentView() 81 | } 82 | } 83 | 84 | extension ContentView { 85 | private var buttons: some View { 86 | VStack(alignment: .leading, spacing: 10) { 87 | Button { 88 | restoreSettings() 89 | } label: { 90 | Label("None", systemImage: "camera.metering.none") 91 | } 92 | .disabled(self.buttonDisabled) 93 | 94 | Button { 95 | buttonDisabled.toggle() 96 | withAnimation(.spring(response: 1, dampingFraction: 0.65, blendDuration: 0.01)) { 97 | self.currentIsland = .batteryView 98 | self.showMusicdetailInfo = false 99 | self.showMusicInfo = false 100 | self.showMusicScaleDetailInfo = false 101 | } 102 | 103 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { 104 | withAnimation(.easeInOut) { 105 | self.showBatteryInfo = true 106 | } 107 | buttonDisabled.toggle() 108 | } 109 | } label: { 110 | Label("Battery View", systemImage: "battery.100") 111 | } 112 | .disabled(self.buttonDisabled) 113 | 114 | Button { 115 | buttonDisabled.toggle() 116 | withAnimation(.spring(response: 1, dampingFraction: 0.65, blendDuration: 0.01)) { 117 | self.currentIsland = .music 118 | self.showBatteryInfo = false 119 | } 120 | 121 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { 122 | withAnimation(.easeInOut) { 123 | self.showMusicInfo = true 124 | } 125 | } 126 | 127 | DispatchQueue.main.asyncAfter(deadline: .now() + 2) { 128 | withAnimation(.spring()) { 129 | self.showMusicdetailInfo = true 130 | } 131 | } 132 | 133 | DispatchQueue.main.asyncAfter(deadline: .now() + 4) { 134 | withAnimation(.spring()) { 135 | self.showMusicScaleDetailInfo = true 136 | } 137 | buttonDisabled.toggle() 138 | } 139 | } label: { 140 | Label("Music View", systemImage: "music.note") 141 | } 142 | .disabled(self.buttonDisabled) 143 | } 144 | } 145 | } 146 | 147 | extension ContentView { 148 | private func restoreSettings() { 149 | self.showBatteryInfo = false 150 | 151 | self.showMusicScaleDetailInfo = false 152 | withAnimation(.spring()) { 153 | self.currentIsland = .none 154 | self.showMusicdetailInfo = false 155 | self.showMusicInfo = false 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /dynamic-island-animation-demo/Shared/Helper/BackgroundView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BackgroundView.swift 3 | // dynamic-island-animation-demo 4 | // 5 | // Created by Runhua Huang on 2022/9/9. 6 | // 7 | 8 | import SwiftUI 9 | 10 | 11 | struct BackgroundView: View { 12 | var cornerRadius: CGFloat = 30 13 | var body: some View { 14 | RoundedRectangle(cornerRadius: self.cornerRadius) 15 | } 16 | } 17 | 18 | struct BackgroundView_Previews: PreviewProvider { 19 | static var previews: some View { 20 | BackgroundView() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /dynamic-island-animation-demo/Shared/Helper/BatteryView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BatteryView.swift 3 | // dynamic-island-animation-demo 4 | // 5 | // Created by Runhua Huang on 9/10/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct BatteryView: View { 11 | 12 | private var batteryOverlayColor: Color = Color(red: 0.561, green: 0.561, blue: 0.561, opacity: 1) 13 | private var batteryColor: Color = Color(red: 0.396, green: 0.769, blue: 0.4, opacity: 1) 14 | 15 | var body: some View { 16 | HStack { 17 | 18 | Text("75%") 19 | .foregroundColor(self.batteryColor) 20 | HStack(spacing: -5) { 21 | 22 | ZStack(alignment: .leading) { 23 | RoundedRectangle(cornerRadius: 5) 24 | .frame(width: 40, height: 20) 25 | .foregroundColor(.gray) 26 | 27 | RoundedRectangle(cornerRadius: 5) 28 | .stroke(self.batteryOverlayColor, lineWidth: 5) 29 | .background(self.batteryColor) 30 | .frame(width: 0.4*85, height: 20) 31 | .cornerRadius(5) 32 | } 33 | 34 | Circle() 35 | .trim(from: 0, to: 0.4) 36 | .rotation(Angle.init(degrees: -72)) 37 | .foregroundColor(.gray) 38 | .frame(width: 10, height: 20) 39 | } 40 | } 41 | } 42 | } 43 | 44 | struct BatteryView_Previews: PreviewProvider { 45 | static var previews: some View { 46 | BatteryView() 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /dynamic-island-animation-demo/Shared/Preference.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Preference.swift 3 | // dynamic-island-animation-demo 4 | // 5 | // Created by Runhua Huang on 9/10/22. 6 | // 7 | 8 | import Foundation 9 | 10 | enum IslandMode { 11 | case none 12 | case batteryView 13 | case music 14 | } 15 | -------------------------------------------------------------------------------- /dynamic-island-animation-demo/Shared/Views/BasicView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BasicView.swift 3 | // dynamic-island-animation-demo 4 | // 5 | // Created by Runhua Huang on 2022/9/9. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct BasicView: View { 11 | var body: some View { 12 | BackgroundView(cornerRadius: 30) 13 | .frame(width: 160, height: 60) 14 | } 15 | } 16 | 17 | struct BasicView_Previews: PreviewProvider { 18 | static var previews: some View { 19 | BasicView() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /dynamic-island-animation-demo/Shared/Views/DynamicIslandBatteryView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DynamicIslandBatteryView.swift 3 | // dynamic-island-animation-demo 4 | // 5 | // Created by Runhua Huang on 9/10/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct DynamicIslandBatteryView: View { 11 | var body: some View { 12 | HStack { 13 | Text("Charging") 14 | .foregroundColor(.white) 15 | 16 | Spacer() 17 | 18 | BatteryView() 19 | } 20 | .padding() 21 | } 22 | } 23 | 24 | struct DynamicIslandBatteryView_Previews: PreviewProvider { 25 | static var previews: some View { 26 | DynamicIslandBatteryView() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /dynamic-island-animation-demo/Shared/Views/MusicView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MusicView.swift 3 | // dynamic-island-animation-demo 4 | // 5 | // Created by Runhua Huang on 9/10/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct MusicView: View { 11 | 12 | @Namespace private var shapeTransition 13 | 14 | @Binding var showScaleView: Bool 15 | @Binding var showMusicdetialInfo: Bool 16 | 17 | var body: some View { 18 | if showScaleView { 19 | ZStack { 20 | HStack(alignment: .top) { 21 | HStack { 22 | Image("music") 23 | .resizable() 24 | .aspectRatio(contentMode: .fit) 25 | .cornerRadius(15) 26 | .matchedGeometryEffect(id: "albumview", in: shapeTransition) 27 | .frame(width: 70, height: 70) 28 | VStack(alignment: .leading) { 29 | Text("Entropy") 30 | .foregroundColor(.white) 31 | .fontWeight(.bold) 32 | Text("Beach Bunny") 33 | .foregroundColor(.gray) 34 | } 35 | } 36 | 37 | Spacer() 38 | 39 | chartView 40 | .matchedGeometryEffect(id: "chartview", in: shapeTransition) 41 | } 42 | 43 | HStack { 44 | Text("1:50") 45 | .foregroundColor(.gray) 46 | .font(.system(size: 10)) 47 | 48 | ProgressView(value: 0.5) 49 | .progressViewStyle(.linear) 50 | .tint(.white) 51 | .background(Color.gray) 52 | 53 | Text("-1:51") 54 | .foregroundColor(.gray) 55 | .font(.system(size: 10)) 56 | } 57 | .offset(x: 0, y: 60) 58 | .opacity(self.showMusicdetialInfo ? 1: 0) 59 | 60 | HStack { 61 | Image(systemName: "airplayaudio") 62 | .resizable() 63 | .aspectRatio(contentMode: .fit) 64 | .foregroundColor(.white) 65 | .frame(width: 20, height: 20) 66 | .opacity(0) 67 | Spacer() 68 | 69 | HStack(spacing: 50) { 70 | Image(systemName: "backward.fill") 71 | .resizable() 72 | .aspectRatio(contentMode: .fit) 73 | .foregroundColor(.white) 74 | .frame(width: 40, height: 40) 75 | 76 | Image(systemName: "play.fill") 77 | .resizable() 78 | .aspectRatio(contentMode: .fit) 79 | .foregroundColor(.white) 80 | .frame(width: 27) 81 | 82 | Image(systemName: "forward.fill") 83 | .resizable() 84 | .aspectRatio(contentMode: .fit) 85 | .foregroundColor(.white) 86 | .frame(width: 40, height: 40) 87 | } 88 | 89 | Spacer() 90 | 91 | Image(systemName: "airplayaudio") 92 | .resizable() 93 | .aspectRatio(contentMode: .fit) 94 | .foregroundColor(.white) 95 | .frame(width: 20, height: 20) 96 | } 97 | .offset(x: 0, y: 110) 98 | .opacity(self.showMusicdetialInfo ? 1: 0) 99 | 100 | Spacer() 101 | } 102 | .padding() 103 | } else { 104 | ZStack { 105 | HStack(alignment: .top) { 106 | HStack { 107 | Image("music") 108 | .resizable() 109 | .aspectRatio(contentMode: .fit) 110 | .cornerRadius(15) 111 | .matchedGeometryEffect(id: "albumview", in: shapeTransition) 112 | .frame(width: 40, height: 40) 113 | VStack(alignment: .leading) { 114 | Text("Entropy") 115 | .foregroundColor(.white) 116 | .fontWeight(.bold) 117 | Text("Beach Bunny") 118 | .foregroundColor(.gray) 119 | } 120 | .opacity(0) 121 | } 122 | 123 | Spacer() 124 | .opacity(0) 125 | 126 | chartView 127 | .matchedGeometryEffect(id: "chartview", in: shapeTransition) 128 | } 129 | 130 | HStack { 131 | Text("1:50") 132 | .foregroundColor(.gray) 133 | .font(.system(size: 10)) 134 | 135 | ProgressView(value: 0.5) 136 | .progressViewStyle(.linear) 137 | .tint(.white) 138 | .background(Color.gray) 139 | 140 | Text("-1:51") 141 | .foregroundColor(.gray) 142 | .font(.system(size: 10)) 143 | } 144 | .offset(x: 0, y: 60) 145 | .hidden() 146 | 147 | HStack { 148 | Image(systemName: "airplayaudio") 149 | .resizable() 150 | .aspectRatio(contentMode: .fit) 151 | .foregroundColor(.white) 152 | .frame(width: 20, height: 20) 153 | .opacity(0) 154 | Spacer() 155 | 156 | HStack(spacing: 50) { 157 | Image(systemName: "backward.fill") 158 | .resizable() 159 | .aspectRatio(contentMode: .fit) 160 | .foregroundColor(.white) 161 | .frame(width: 40, height: 40) 162 | 163 | Image(systemName: "play.fill") 164 | .resizable() 165 | .aspectRatio(contentMode: .fit) 166 | .foregroundColor(.white) 167 | .frame(width: 27) 168 | 169 | Image(systemName: "forward.fill") 170 | .resizable() 171 | .aspectRatio(contentMode: .fit) 172 | .foregroundColor(.white) 173 | .frame(width: 40, height: 40) 174 | } 175 | 176 | Spacer() 177 | 178 | Image(systemName: "airplayaudio") 179 | .resizable() 180 | .aspectRatio(contentMode: .fit) 181 | .foregroundColor(.white) 182 | .frame(width: 20, height: 20) 183 | } 184 | .offset(x: 0, y: 110) 185 | .hidden() 186 | 187 | Spacer() 188 | } 189 | .padding() 190 | } 191 | 192 | } 193 | } 194 | 195 | struct MusicView_Previews: PreviewProvider { 196 | static var previews: some View { 197 | MusicView(showScaleView: .constant(false), showMusicdetialInfo: .constant(false)) 198 | .preferredColorScheme(.dark) 199 | } 200 | } 201 | 202 | extension MusicView { 203 | private var chartView: some View { 204 | HStack(spacing: 3) { 205 | RoundedRectangle(cornerRadius: 10) 206 | .frame(width: 3, height: 20) 207 | RoundedRectangle(cornerRadius: 10) 208 | .frame(width: 3, height: 25) 209 | RoundedRectangle(cornerRadius: 10) 210 | .frame(width: 3, height: 30) 211 | RoundedRectangle(cornerRadius: 10) 212 | .frame(width: 3, height: 35) 213 | RoundedRectangle(cornerRadius: 10) 214 | .frame(width: 3, height: 23) 215 | RoundedRectangle(cornerRadius: 10) 216 | .frame(width: 3, height: 15) 217 | } 218 | .foregroundColor(.white) 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /dynamic-island-animation-demo/Shared/Views/TestView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestView.swift 3 | // dynamic-island-animation-demo 4 | // 5 | // Created by Runhua Huang on 9/10/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct TestView: View { 11 | 12 | @Namespace private var shapeTransition 13 | 14 | @State var showScaleView: Bool = false 15 | 16 | var body: some View { 17 | VStack { 18 | if showScaleView { 19 | ZStack { 20 | HStack(alignment: .top) { 21 | HStack { 22 | Image("music") 23 | .resizable() 24 | .aspectRatio(contentMode: .fit) 25 | .cornerRadius(15) 26 | .matchedGeometryEffect(id: "albumview", in: shapeTransition) 27 | .frame(width: 70, height: 70) 28 | VStack(alignment: .leading) { 29 | Text("Entropy") 30 | .foregroundColor(.white) 31 | .fontWeight(.bold) 32 | Text("Beach Bunny") 33 | .foregroundColor(.gray) 34 | } 35 | } 36 | 37 | Spacer() 38 | 39 | chartView 40 | .matchedGeometryEffect(id: "chartview", in: shapeTransition) 41 | } 42 | 43 | HStack { 44 | Text("1:50") 45 | .foregroundColor(.gray) 46 | .font(.system(size: 10)) 47 | 48 | ProgressView(value: 0.5) 49 | .progressViewStyle(.linear) 50 | .tint(.white) 51 | .background(Color.gray) 52 | 53 | Text("-1:51") 54 | .foregroundColor(.gray) 55 | .font(.system(size: 10)) 56 | } 57 | .offset(x: 0, y: 60) 58 | 59 | HStack { 60 | Image(systemName: "airplayaudio") 61 | .resizable() 62 | .aspectRatio(contentMode: .fit) 63 | .foregroundColor(.white) 64 | .frame(width: 20, height: 20) 65 | .opacity(0) 66 | Spacer() 67 | 68 | HStack(spacing: 50) { 69 | Image(systemName: "backward.fill") 70 | .resizable() 71 | .aspectRatio(contentMode: .fit) 72 | .foregroundColor(.white) 73 | .frame(width: 40, height: 40) 74 | 75 | Image(systemName: "play.fill") 76 | .resizable() 77 | .aspectRatio(contentMode: .fit) 78 | .foregroundColor(.white) 79 | .frame(width: 27) 80 | 81 | Image(systemName: "forward.fill") 82 | .resizable() 83 | .aspectRatio(contentMode: .fit) 84 | .foregroundColor(.white) 85 | .frame(width: 40, height: 40) 86 | } 87 | 88 | Spacer() 89 | 90 | Image(systemName: "airplayaudio") 91 | .resizable() 92 | .aspectRatio(contentMode: .fit) 93 | .foregroundColor(.white) 94 | .frame(width: 20, height: 20) 95 | } 96 | .offset(x: 0, y: 110) 97 | } 98 | .padding(21) 99 | } else { 100 | ZStack { 101 | HStack(alignment: .top) { 102 | HStack { 103 | Image("music") 104 | .resizable() 105 | .aspectRatio(contentMode: .fit) 106 | .cornerRadius(15) 107 | .matchedGeometryEffect(id: "albumview", in: shapeTransition) 108 | .frame(width: 40, height: 40) 109 | 110 | VStack(alignment: .leading) { 111 | Text("Entropy") 112 | .foregroundColor(.white) 113 | .fontWeight(.bold) 114 | Text("Beach Bunny") 115 | .foregroundColor(.gray) 116 | } 117 | .opacity(0) 118 | } 119 | 120 | Spacer() 121 | .opacity(0) 122 | 123 | chartView 124 | .matchedGeometryEffect(id: "chartview", in: shapeTransition) 125 | } 126 | 127 | HStack { 128 | Text("1:50") 129 | .foregroundColor(.gray) 130 | .font(.system(size: 10)) 131 | 132 | ProgressView(value: 0.5) 133 | .progressViewStyle(.linear) 134 | .tint(.white) 135 | .background(Color.gray) 136 | 137 | Text("-1:51") 138 | .foregroundColor(.gray) 139 | .font(.system(size: 10)) 140 | } 141 | .offset(x: 0, y: 60) 142 | .hidden() 143 | 144 | HStack { 145 | Image(systemName: "airplayaudio") 146 | .resizable() 147 | .aspectRatio(contentMode: .fit) 148 | .foregroundColor(.white) 149 | .frame(width: 20, height: 20) 150 | .opacity(0) 151 | Spacer() 152 | 153 | HStack(spacing: 50) { 154 | Image(systemName: "backward.fill") 155 | .resizable() 156 | .aspectRatio(contentMode: .fit) 157 | .foregroundColor(.white) 158 | .frame(width: 40, height: 40) 159 | 160 | Image(systemName: "play.fill") 161 | .resizable() 162 | .aspectRatio(contentMode: .fit) 163 | .foregroundColor(.white) 164 | .frame(width: 27) 165 | 166 | Image(systemName: "forward.fill") 167 | .resizable() 168 | .aspectRatio(contentMode: .fit) 169 | .foregroundColor(.white) 170 | .frame(width: 40, height: 40) 171 | } 172 | 173 | Spacer() 174 | 175 | Image(systemName: "airplayaudio") 176 | .resizable() 177 | .aspectRatio(contentMode: .fit) 178 | .foregroundColor(.white) 179 | .frame(width: 20, height: 20) 180 | } 181 | .offset(x: 0, y: 110) 182 | .hidden() 183 | 184 | 185 | } 186 | .padding() 187 | } 188 | 189 | Spacer() 190 | 191 | Button { 192 | withAnimation(.default) { 193 | self.showScaleView.toggle() 194 | } 195 | } label: { 196 | Text("Scale") 197 | } 198 | 199 | } 200 | 201 | } 202 | } 203 | 204 | struct TestView_Previews: PreviewProvider { 205 | static var previews: some View { 206 | TestView() 207 | .preferredColorScheme(.dark) 208 | } 209 | } 210 | 211 | extension TestView { 212 | private var chartView: some View { 213 | HStack(spacing: 3) { 214 | RoundedRectangle(cornerRadius: 10) 215 | .frame(width: 3, height: 20) 216 | RoundedRectangle(cornerRadius: 10) 217 | .frame(width: 3, height: 25) 218 | RoundedRectangle(cornerRadius: 10) 219 | .frame(width: 3, height: 30) 220 | RoundedRectangle(cornerRadius: 10) 221 | .frame(width: 3, height: 35) 222 | RoundedRectangle(cornerRadius: 10) 223 | .frame(width: 3, height: 23) 224 | RoundedRectangle(cornerRadius: 10) 225 | .frame(width: 3, height: 15) 226 | } 227 | .foregroundColor(.white) 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /dynamic-island-animation-demo/Shared/dynamic_island_animation_demoApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // dynamic_island_animation_demoApp.swift 3 | // Shared 4 | // 5 | // Created by Runhua Huang on 2022/9/9. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct dynamic_island_animation_demoApp: App { 12 | var body: some Scene { 13 | WindowGroup { 14 | ContentView() 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /dynamic-island-animation-demo/dynamic-island-animation-demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /dynamic-island-animation-demo/dynamic-island-animation-demo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /dynamic-island-animation-demo/dynamic-island-animation-demo.xcodeproj/project.xcworkspace/xcuserdata/runhuahuang.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuangRunHua/swiftui-animation-demo/a1b1af789c13ac08c649157170d341770011d54e/dynamic-island-animation-demo/dynamic-island-animation-demo.xcodeproj/project.xcworkspace/xcuserdata/runhuahuang.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /dynamic-island-animation-demo/dynamic-island-animation-demo.xcodeproj/xcuserdata/runhuahuang.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | dynamic-island-animation-demo (iOS).xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | dynamic-island-animation-demo (macOS).xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 1 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /dynamic-island-animation-demo/macOS/macOS.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /iMessageBubbleAnimation.swiftpm/.swiftpm/playgrounds/CachedManifest.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CachedManifest 6 | 7 | manifestData 8 | 9 | eyJkZXBlbmRlbmNpZXMiOltdLCJkaXNwbGF5TmFtZSI6ImlNZXNzYWdlQnVi 10 | YmxlIiwicGFja2FnZUtpbmQiOnsicm9vdCI6e319LCJwbGF0Zm9ybXMiOlt7 11 | Im9wdGlvbnMiOltdLCJwbGF0Zm9ybU5hbWUiOiJpb3MiLCJ2ZXJzaW9uIjoi 12 | MTUuMiJ9XSwicHJvZHVjdHMiOlt7Im5hbWUiOiJpTWVzc2FnZUJ1YmJsZSIs 13 | InNldHRpbmdzIjpbeyJkaXNwbGF5VmVyc2lvbiI6WyIxLjAiXX0seyJidW5k 14 | bGVWZXJzaW9uIjpbIjEiXX0seyJpT1NBcHBJbmZvIjpbeyJhY2NlbnRDb2xv 15 | ciI6eyJwcmVzZXRDb2xvciI6eyJwcmVzZXRDb2xvciI6eyJyYXdWYWx1ZSI6 16 | InRlYWwifX19LCJhcHBJY29uIjp7InBsYWNlaG9sZGVyIjp7Imljb24iOnsi 17 | cmF3VmFsdWUiOiJjaGF0TWVzc2FnZSJ9fX0sImNhcGFiaWxpdGllcyI6W10s 18 | InN1cHBvcnRlZERldmljZUZhbWlsaWVzIjpbInBhZCIsInBob25lIl0sInN1 19 | cHBvcnRlZEludGVyZmFjZU9yaWVudGF0aW9ucyI6W3sicG9ydHJhaXQiOnt9 20 | fSx7ImxhbmRzY2FwZVJpZ2h0Ijp7fX0seyJsYW5kc2NhcGVMZWZ0Ijp7fX0s 21 | eyJwb3J0cmFpdFVwc2lkZURvd24iOnsiY29uZGl0aW9uIjp7ImRldmljZUZh 22 | bWlsaWVzIjpbInBhZCJdfX19XX1dfV0sInRhcmdldHMiOlsiQXBwTW9kdWxl 23 | Il0sInR5cGUiOnsiZXhlY3V0YWJsZSI6bnVsbH19XSwidGFyZ2V0TWFwIjp7 24 | IkFwcE1vZHVsZSI6eyJkZXBlbmRlbmNpZXMiOltdLCJleGNsdWRlIjpbXSwi 25 | bmFtZSI6IkFwcE1vZHVsZSIsInBhdGgiOiIuIiwicmVzb3VyY2VzIjpbXSwi 26 | c2V0dGluZ3MiOltdLCJ0eXBlIjoiZXhlY3V0YWJsZSJ9fSwidGFyZ2V0cyI6 27 | W3siZGVwZW5kZW5jaWVzIjpbXSwiZXhjbHVkZSI6W10sIm5hbWUiOiJBcHBN 28 | b2R1bGUiLCJwYXRoIjoiLiIsInJlc291cmNlcyI6W10sInNldHRpbmdzIjpb 29 | XSwidHlwZSI6ImV4ZWN1dGFibGUifV0sInRvb2xzVmVyc2lvbiI6eyJfdmVy 30 | c2lvbiI6IjUuNi4wIn19 31 | 32 | manifestHash 33 | 34 | XwdhgW85y8/j5q7ngfOcZ0nHT0NoFD4KVyETPjAKQco= 35 | 36 | schemaVersion 37 | 4 38 | swiftPMVersionString 39 | 5.6.0-dev 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /iMessageBubbleAnimation.swiftpm/.swiftpm/playgrounds/DocumentThumbnail.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | DocumentThumbnailConfiguration 6 | 7 | accentColorHash 8 | 9 | JlU5ms+Ze8KtU0bsogWosZRedIMdSqxxyvvP4qc/CT4= 10 | 11 | appIconHash 12 | 13 | zJbZa+McWP2eWUKTifSXDeqGTQNyPzB4IOVQKxO9hgw= 14 | 15 | thumbnailIsPrerendered 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /iMessageBubbleAnimation.swiftpm/.swiftpm/playgrounds/DocumentThumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuangRunHua/swiftui-animation-demo/a1b1af789c13ac08c649157170d341770011d54e/iMessageBubbleAnimation.swiftpm/.swiftpm/playgrounds/DocumentThumbnail.png -------------------------------------------------------------------------------- /iMessageBubbleAnimation.swiftpm/.swiftpm/playgrounds/Workspace.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | AppSettings 6 | 7 | appIconPlaceholderGlyphName 8 | chat-message 9 | appSettingsVersion 10 | 1 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /iMessageBubbleAnimation.swiftpm/ContentView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct ContentView: View { 4 | 5 | @State private var bubbleScaleRate: Double = 1.0 6 | @State private var dotColor: [Color] = [ 7 | Color(red: 0.81, green: 0.82, blue: 0.84), 8 | Color(red: 0.72, green: 0.73, blue: 0.74), 9 | Color(red: 0.6, green: 0.6, blue: 0.61), 10 | ] 11 | @State private var dotsColorIndex: [Int] = [0, 1, 2] 12 | 13 | private var repeatingAnimation: Animation { 14 | Animation 15 | .easeInOut(duration: 1) 16 | .repeatForever() 17 | } 18 | 19 | private var dotColorChangeAnimation: Animation { 20 | Animation 21 | .easeIn(duration: 0.5) 22 | .repeatForever() 23 | } 24 | 25 | private var circleView: some View { 26 | Circle() 27 | .frame(width: 25, height: 25) 28 | } 29 | 30 | 31 | var body: some View { 32 | ZStack { 33 | ZStack { 34 | RoundedRectangle(cornerRadius: 43) 35 | .foregroundColor(Color(red: 0.86, green: 0.87, blue: 0.88)) 36 | .background(.quaternary, in: Capsule()) 37 | .frame(width: 150, height: 86) 38 | HStack(spacing: 5) { 39 | circleView 40 | .foregroundColor(dotColor[dotsColorIndex[0]]) 41 | circleView 42 | .foregroundColor(dotColor[dotsColorIndex[1]]) 43 | circleView 44 | .foregroundColor(dotColor[dotsColorIndex[2]]) 45 | } 46 | } 47 | Circle() 48 | .foregroundColor(Color(red: 0.86, green: 0.87, blue: 0.88)) 49 | .frame(width: 30, height: 30) 50 | .offset(x: -60, y: 35) 51 | 52 | Circle() 53 | .foregroundColor(Color(red: 0.86, green: 0.87, blue: 0.88)) 54 | .frame(width: 15, height: 15) 55 | .offset(x: -80, y: 55) 56 | } 57 | .scaleEffect(self.bubbleScaleRate) 58 | .onAppear { 59 | withAnimation(self.repeatingAnimation) { 60 | self.bubbleScaleRate = 1.1 61 | } 62 | 63 | withAnimation(self.dotColorChangeAnimation) { 64 | dotsColorIndex[0] = 2 65 | dotsColorIndex[1] = 0 66 | dotsColorIndex[2] = 1 67 | } 68 | 69 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) { 70 | withAnimation(self.dotColorChangeAnimation) { 71 | dotsColorIndex[0] = 1 72 | dotsColorIndex[1] = 2 73 | dotsColorIndex[2] = 0 74 | } 75 | } 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /iMessageBubbleAnimation.swiftpm/MyApp.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | @main 4 | struct MyApp: App { 5 | var body: some Scene { 6 | WindowGroup { 7 | ContentView() 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /iMessageBubbleAnimation.swiftpm/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.6 2 | 3 | // WARNING: 4 | // This file is automatically generated. 5 | // Do not edit it by hand because the contents will be replaced. 6 | 7 | import PackageDescription 8 | import AppleProductTypes 9 | 10 | let package = Package( 11 | name: "iMessageBubble", 12 | platforms: [ 13 | .iOS("15.2") 14 | ], 15 | products: [ 16 | .iOSApplication( 17 | name: "iMessageBubble", 18 | targets: ["AppModule"], 19 | displayVersion: "1.0", 20 | bundleVersion: "1", 21 | appIcon: .placeholder(icon: .chatMessage), 22 | accentColor: .presetColor(.teal), 23 | supportedDeviceFamilies: [ 24 | .pad, 25 | .phone 26 | ], 27 | supportedInterfaceOrientations: [ 28 | .portrait, 29 | .landscapeRight, 30 | .landscapeLeft, 31 | .portraitUpsideDown(.when(deviceFamilies: [.pad])) 32 | ] 33 | ) 34 | ], 35 | targets: [ 36 | .executableTarget( 37 | name: "AppModule", 38 | path: "." 39 | ) 40 | ] 41 | ) 42 | --------------------------------------------------------------------------------