├── .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 | 
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 |
--------------------------------------------------------------------------------