├── .gitignore
├── GradientDescend.swiftpm.zip
├── GradientDescend.swiftpm
├── .swiftpm
│ ├── playgrounds
│ │ ├── CachedManifest.plist
│ │ ├── DocumentThumbnail.plist
│ │ ├── DocumentThumbnail.png
│ │ └── Workspace.plist
│ └── xcode
│ │ ├── package.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcuserdata
│ │ │ └── ryendu.xcuserdatad
│ │ │ └── UserInterfaceState.xcuserstate
│ │ └── xcuserdata
│ │ └── ryendu.xcuserdatad
│ │ ├── xcdebugger
│ │ └── Breakpoints_v2.xcbkptlist
│ │ └── xcschemes
│ │ └── xcschememanagement.plist
├── Assets.xcassets
│ ├── AccentColor.colorset
│ │ └── Contents.json
│ ├── AppIcon.appiconset
│ │ ├── 100.png
│ │ ├── 1024.png
│ │ ├── 114.png
│ │ ├── 120.png
│ │ ├── 128.png
│ │ ├── 144.png
│ │ ├── 152.png
│ │ ├── 16.png
│ │ ├── 167.png
│ │ ├── 180.png
│ │ ├── 20.png
│ │ ├── 256.png
│ │ ├── 29.png
│ │ ├── 32.png
│ │ ├── 40.png
│ │ ├── 50.png
│ │ ├── 512.png
│ │ ├── 57.png
│ │ ├── 58.png
│ │ ├── 60.png
│ │ ├── 64.png
│ │ ├── 72.png
│ │ ├── 76.png
│ │ ├── 80.png
│ │ ├── 87.png
│ │ └── Contents.json
│ ├── Contents.json
│ ├── bg1.colorset
│ │ └── Contents.json
│ ├── bg2.colorset
│ │ └── Contents.json
│ ├── bg3.colorset
│ │ └── Contents.json
│ ├── bg4.colorset
│ │ └── Contents.json
│ ├── bg5.colorset
│ │ └── Contents.json
│ ├── deviceFrame.colorset
│ │ └── Contents.json
│ ├── gd1-1.colorset
│ │ └── Contents.json
│ ├── gd1-2.colorset
│ │ └── Contents.json
│ ├── gd1-3.colorset
│ │ └── Contents.json
│ ├── gd1-4.colorset
│ │ └── Contents.json
│ ├── gd1-5.colorset
│ │ └── Contents.json
│ ├── neutral100.colorset
│ │ └── Contents.json
│ ├── neutral200.colorset
│ │ └── Contents.json
│ ├── neutral300.colorset
│ │ └── Contents.json
│ ├── neutral400.colorset
│ │ └── Contents.json
│ ├── neutral500.colorset
│ │ └── Contents.json
│ ├── neutral600.colorset
│ │ └── Contents.json
│ ├── neutral700.colorset
│ │ └── Contents.json
│ ├── neutral800.colorset
│ │ └── Contents.json
│ ├── neutral900.colorset
│ │ └── Contents.json
│ ├── neutralStrong.colorset
│ │ └── Contents.json
│ ├── neutralWeak.colorset
│ │ └── Contents.json
│ ├── shade1.imageset
│ │ ├── Contents.json
│ │ └── Screen Shot 2022-04-21 at 8.11.49 PM.png
│ └── wwdc.imageset
│ │ ├── Contents.json
│ │ └── wwdc.png
├── Package.swift
├── Packages
│ ├── ConfettiSwiftUI
│ │ ├── .gitignore
│ │ ├── .swiftpm
│ │ │ └── xcode
│ │ │ │ ├── package.xcworkspace
│ │ │ │ ├── contents.xcworkspacedata
│ │ │ │ └── xcshareddata
│ │ │ │ │ └── IDEWorkspaceChecks.plist
│ │ │ │ └── xcshareddata
│ │ │ │ └── xcschemes
│ │ │ │ └── ConfettiSwiftUI.xcscheme
│ │ ├── LICENSE_C
│ │ ├── README_C.md
│ │ └── Sources
│ │ │ ├── ConfettiSwiftUI.swift
│ │ │ ├── Shapes
│ │ │ ├── RoundedCross.swift
│ │ │ ├── SlimRectangle.swift
│ │ │ └── Triangle.swift
│ │ │ └── View+ConfettiCannon.swift
│ └── IrregularGradient
│ │ ├── .gitignore
│ │ ├── .swiftpm
│ │ └── xcode
│ │ │ └── package.xcworkspace
│ │ │ ├── contents.xcworkspacedata
│ │ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ │ ├── LICENSE_I
│ │ ├── README_I.md
│ │ └── Sources
│ │ └── IrregularGradient
│ │ ├── Blob.swift
│ │ ├── IrregularGradient.swift
│ │ ├── IrregularGradientView.swift
│ │ └── Modifiers.swift
├── Resources
│ ├── intro.m4a
│ └── simple3dgd.scn
└── Views
│ ├── 2D
│ ├── 2DGraph.swift
│ ├── LearnView.swift
│ └── Slides2D.swift
│ ├── 3D
│ ├── 3DGraph.swift
│ ├── MultiDimensionalGDView.swift
│ └── Slides3D.swift
│ ├── ContentView.swift
│ ├── MyApp.swift
│ ├── Other
│ ├── Basic2DView.swift
│ ├── InteractiveWindow.swift
│ ├── OnboardingView.swift
│ └── SupportingViews.swift
│ └── TabView.swift
├── LICENSE
├── README.md
├── Screen Shot 2022-04-24 at 10.00.09 PM (2).png
├── gradientDescent3dSimple.blend
├── gradientDescent3dSimple.blend1
└── gradientDescent3dSimple.mtl
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ryendu/GradientDescend/c751956c75a9ee1a4b8338a4323f82296ab18c44/GradientDescend.swiftpm.zip
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/.swiftpm/playgrounds/CachedManifest.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CachedManifest
6 |
7 | manifestData
8 |
9 | eyJkZXBlbmRlbmNpZXMiOltdLCJuYW1lIjoiR3JhZGllbnREZXNjZW5kIiwi
10 | cGFja2FnZUtpbmQiOiJyb290IiwicGxhdGZvcm1zIjpbeyJvcHRpb25zIjpb
11 | XSwicGxhdGZvcm1OYW1lIjoiaW9zIiwidmVyc2lvbiI6IjE1LjIifV0sInBy
12 | b2R1Y3RzIjpbeyJuYW1lIjoiR3JhZGllbnREZXNjZW5kIiwic2V0dGluZ3Mi
13 | Olt7ImRpc3BsYXlWZXJzaW9uIjpbIjEuMCJdfSx7ImJ1bmRsZVZlcnNpb24i
14 | OlsiMSJdfSx7ImlPU0FwcEluZm8iOlt7ImFjY2VudENvbG9yQXNzZXROYW1l
15 | IjoiQWNjZW50Q29sb3IiLCJjYXBhYmlsaXRpZXMiOltdLCJpY29uQXNzZXRO
16 | YW1lIjoiQXBwSWNvbiIsInN1cHBvcnRlZERldmljZUZhbWlsaWVzIjpbInBh
17 | ZCIsInBob25lIl0sInN1cHBvcnRlZEludGVyZmFjZU9yaWVudGF0aW9ucyI6
18 | W3sicG9ydHJhaXQiOnt9fSx7ImxhbmRzY2FwZVJpZ2h0Ijp7fX0seyJsYW5k
19 | c2NhcGVMZWZ0Ijp7fX0seyJwb3J0cmFpdFVwc2lkZURvd24iOnsiY29uZGl0
20 | aW9uIjp7ImRldmljZUZhbWlsaWVzIjpbInBhZCJdfX19XX1dfV0sInRhcmdl
21 | dHMiOlsiQXBwTW9kdWxlIl0sInR5cGUiOnsiZXhlY3V0YWJsZSI6bnVsbH19
22 | XSwidGFyZ2V0TWFwIjp7IkFwcE1vZHVsZSI6eyJkZXBlbmRlbmNpZXMiOltd
23 | LCJleGNsdWRlIjpbXSwibmFtZSI6IkFwcE1vZHVsZSIsInBhdGgiOiIuIiwi
24 | cmVzb3VyY2VzIjpbXSwic2V0dGluZ3MiOltdLCJ0eXBlIjoiZXhlY3V0YWJs
25 | ZSJ9fSwidGFyZ2V0cyI6W3siZGVwZW5kZW5jaWVzIjpbXSwiZXhjbHVkZSI6
26 | W10sIm5hbWUiOiJBcHBNb2R1bGUiLCJwYXRoIjoiLiIsInJlc291cmNlcyI6
27 | W10sInNldHRpbmdzIjpbXSwidHlwZSI6ImV4ZWN1dGFibGUifV0sInRvb2xz
28 | VmVyc2lvbiI6eyJfdmVyc2lvbiI6IjUuNS4wIn19
29 |
30 | manifestHash
31 |
32 | t99qV8PfFNkSbWIolFoFudxjeu/XmV7HS3WGBYBccdo=
33 |
34 | schemaVersion
35 | 3
36 | swiftPMVersionString
37 | 5.5.0
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/.swiftpm/playgrounds/DocumentThumbnail.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | DocumentThumbnailConfiguration
6 |
7 | accentColorHash
8 |
9 | jgobCtpCFyiG/RKX4lq/mfFDlqlACsvV8g2iAonP8C8=
10 |
11 | appIconHash
12 |
13 | elF57swP4YdgumFfkmAzcq4/4wKGAJigGeFZJ1Uf7js=
14 |
15 | thumbnailIsPrerendered
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/.swiftpm/playgrounds/DocumentThumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ryendu/GradientDescend/c751956c75a9ee1a4b8338a4323f82296ab18c44/GradientDescend.swiftpm/.swiftpm/playgrounds/DocumentThumbnail.png
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/.swiftpm/playgrounds/Workspace.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | AppSettings
6 |
7 | appIconPlaceholderGlyphName
8 | bird
9 | appSettingsVersion
10 | 1
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/.swiftpm/xcode/package.xcworkspace/xcuserdata/ryendu.xcuserdatad/UserInterfaceState.xcuserstate:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ryendu/GradientDescend/c751956c75a9ee1a4b8338a4323f82296ab18c44/GradientDescend.swiftpm/.swiftpm/xcode/package.xcworkspace/xcuserdata/ryendu.xcuserdatad/UserInterfaceState.xcuserstate
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/.swiftpm/xcode/xcuserdata/ryendu.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
9 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/.swiftpm/xcode/xcuserdata/ryendu.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | GradientDescend.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 0
11 |
12 |
13 | SuppressBuildableAutocreation
14 |
15 | AppModule
16 |
17 | primary
18 |
19 |
20 | GradientDescend
21 |
22 | primary
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0xE1",
9 | "green" : "0xEC",
10 | "red" : "0x34"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Assets.xcassets/AppIcon.appiconset/100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ryendu/GradientDescend/c751956c75a9ee1a4b8338a4323f82296ab18c44/GradientDescend.swiftpm/Assets.xcassets/AppIcon.appiconset/100.png
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Assets.xcassets/AppIcon.appiconset/1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ryendu/GradientDescend/c751956c75a9ee1a4b8338a4323f82296ab18c44/GradientDescend.swiftpm/Assets.xcassets/AppIcon.appiconset/1024.png
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Assets.xcassets/AppIcon.appiconset/114.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ryendu/GradientDescend/c751956c75a9ee1a4b8338a4323f82296ab18c44/GradientDescend.swiftpm/Assets.xcassets/AppIcon.appiconset/114.png
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Assets.xcassets/AppIcon.appiconset/120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ryendu/GradientDescend/c751956c75a9ee1a4b8338a4323f82296ab18c44/GradientDescend.swiftpm/Assets.xcassets/AppIcon.appiconset/120.png
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Assets.xcassets/AppIcon.appiconset/128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ryendu/GradientDescend/c751956c75a9ee1a4b8338a4323f82296ab18c44/GradientDescend.swiftpm/Assets.xcassets/AppIcon.appiconset/128.png
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Assets.xcassets/AppIcon.appiconset/144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ryendu/GradientDescend/c751956c75a9ee1a4b8338a4323f82296ab18c44/GradientDescend.swiftpm/Assets.xcassets/AppIcon.appiconset/144.png
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Assets.xcassets/AppIcon.appiconset/152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ryendu/GradientDescend/c751956c75a9ee1a4b8338a4323f82296ab18c44/GradientDescend.swiftpm/Assets.xcassets/AppIcon.appiconset/152.png
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Assets.xcassets/AppIcon.appiconset/16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ryendu/GradientDescend/c751956c75a9ee1a4b8338a4323f82296ab18c44/GradientDescend.swiftpm/Assets.xcassets/AppIcon.appiconset/16.png
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Assets.xcassets/AppIcon.appiconset/167.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ryendu/GradientDescend/c751956c75a9ee1a4b8338a4323f82296ab18c44/GradientDescend.swiftpm/Assets.xcassets/AppIcon.appiconset/167.png
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Assets.xcassets/AppIcon.appiconset/180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ryendu/GradientDescend/c751956c75a9ee1a4b8338a4323f82296ab18c44/GradientDescend.swiftpm/Assets.xcassets/AppIcon.appiconset/180.png
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Assets.xcassets/AppIcon.appiconset/20.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ryendu/GradientDescend/c751956c75a9ee1a4b8338a4323f82296ab18c44/GradientDescend.swiftpm/Assets.xcassets/AppIcon.appiconset/20.png
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Assets.xcassets/AppIcon.appiconset/256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ryendu/GradientDescend/c751956c75a9ee1a4b8338a4323f82296ab18c44/GradientDescend.swiftpm/Assets.xcassets/AppIcon.appiconset/256.png
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Assets.xcassets/AppIcon.appiconset/29.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ryendu/GradientDescend/c751956c75a9ee1a4b8338a4323f82296ab18c44/GradientDescend.swiftpm/Assets.xcassets/AppIcon.appiconset/29.png
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Assets.xcassets/AppIcon.appiconset/32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ryendu/GradientDescend/c751956c75a9ee1a4b8338a4323f82296ab18c44/GradientDescend.swiftpm/Assets.xcassets/AppIcon.appiconset/32.png
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Assets.xcassets/AppIcon.appiconset/40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ryendu/GradientDescend/c751956c75a9ee1a4b8338a4323f82296ab18c44/GradientDescend.swiftpm/Assets.xcassets/AppIcon.appiconset/40.png
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Assets.xcassets/AppIcon.appiconset/50.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ryendu/GradientDescend/c751956c75a9ee1a4b8338a4323f82296ab18c44/GradientDescend.swiftpm/Assets.xcassets/AppIcon.appiconset/50.png
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Assets.xcassets/AppIcon.appiconset/512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ryendu/GradientDescend/c751956c75a9ee1a4b8338a4323f82296ab18c44/GradientDescend.swiftpm/Assets.xcassets/AppIcon.appiconset/512.png
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Assets.xcassets/AppIcon.appiconset/57.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ryendu/GradientDescend/c751956c75a9ee1a4b8338a4323f82296ab18c44/GradientDescend.swiftpm/Assets.xcassets/AppIcon.appiconset/57.png
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Assets.xcassets/AppIcon.appiconset/58.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ryendu/GradientDescend/c751956c75a9ee1a4b8338a4323f82296ab18c44/GradientDescend.swiftpm/Assets.xcassets/AppIcon.appiconset/58.png
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Assets.xcassets/AppIcon.appiconset/60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ryendu/GradientDescend/c751956c75a9ee1a4b8338a4323f82296ab18c44/GradientDescend.swiftpm/Assets.xcassets/AppIcon.appiconset/60.png
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Assets.xcassets/AppIcon.appiconset/64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ryendu/GradientDescend/c751956c75a9ee1a4b8338a4323f82296ab18c44/GradientDescend.swiftpm/Assets.xcassets/AppIcon.appiconset/64.png
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Assets.xcassets/AppIcon.appiconset/72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ryendu/GradientDescend/c751956c75a9ee1a4b8338a4323f82296ab18c44/GradientDescend.swiftpm/Assets.xcassets/AppIcon.appiconset/72.png
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Assets.xcassets/AppIcon.appiconset/76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ryendu/GradientDescend/c751956c75a9ee1a4b8338a4323f82296ab18c44/GradientDescend.swiftpm/Assets.xcassets/AppIcon.appiconset/76.png
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Assets.xcassets/AppIcon.appiconset/80.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ryendu/GradientDescend/c751956c75a9ee1a4b8338a4323f82296ab18c44/GradientDescend.swiftpm/Assets.xcassets/AppIcon.appiconset/80.png
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Assets.xcassets/AppIcon.appiconset/87.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ryendu/GradientDescend/c751956c75a9ee1a4b8338a4323f82296ab18c44/GradientDescend.swiftpm/Assets.xcassets/AppIcon.appiconset/87.png
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "40.png",
5 | "idiom" : "iphone",
6 | "scale" : "2x",
7 | "size" : "20x20"
8 | },
9 | {
10 | "filename" : "60.png",
11 | "idiom" : "iphone",
12 | "scale" : "3x",
13 | "size" : "20x20"
14 | },
15 | {
16 | "filename" : "29.png",
17 | "idiom" : "iphone",
18 | "scale" : "1x",
19 | "size" : "29x29"
20 | },
21 | {
22 | "filename" : "58.png",
23 | "idiom" : "iphone",
24 | "scale" : "2x",
25 | "size" : "29x29"
26 | },
27 | {
28 | "filename" : "87.png",
29 | "idiom" : "iphone",
30 | "scale" : "3x",
31 | "size" : "29x29"
32 | },
33 | {
34 | "filename" : "80.png",
35 | "idiom" : "iphone",
36 | "scale" : "2x",
37 | "size" : "40x40"
38 | },
39 | {
40 | "filename" : "120.png",
41 | "idiom" : "iphone",
42 | "scale" : "3x",
43 | "size" : "40x40"
44 | },
45 | {
46 | "filename" : "57.png",
47 | "idiom" : "iphone",
48 | "scale" : "1x",
49 | "size" : "57x57"
50 | },
51 | {
52 | "filename" : "114.png",
53 | "idiom" : "iphone",
54 | "scale" : "2x",
55 | "size" : "57x57"
56 | },
57 | {
58 | "filename" : "120.png",
59 | "idiom" : "iphone",
60 | "scale" : "2x",
61 | "size" : "60x60"
62 | },
63 | {
64 | "filename" : "180.png",
65 | "idiom" : "iphone",
66 | "scale" : "3x",
67 | "size" : "60x60"
68 | },
69 | {
70 | "filename" : "20.png",
71 | "idiom" : "ipad",
72 | "scale" : "1x",
73 | "size" : "20x20"
74 | },
75 | {
76 | "filename" : "40.png",
77 | "idiom" : "ipad",
78 | "scale" : "2x",
79 | "size" : "20x20"
80 | },
81 | {
82 | "filename" : "29.png",
83 | "idiom" : "ipad",
84 | "scale" : "1x",
85 | "size" : "29x29"
86 | },
87 | {
88 | "filename" : "58.png",
89 | "idiom" : "ipad",
90 | "scale" : "2x",
91 | "size" : "29x29"
92 | },
93 | {
94 | "filename" : "40.png",
95 | "idiom" : "ipad",
96 | "scale" : "1x",
97 | "size" : "40x40"
98 | },
99 | {
100 | "filename" : "80.png",
101 | "idiom" : "ipad",
102 | "scale" : "2x",
103 | "size" : "40x40"
104 | },
105 | {
106 | "filename" : "50.png",
107 | "idiom" : "ipad",
108 | "scale" : "1x",
109 | "size" : "50x50"
110 | },
111 | {
112 | "filename" : "100.png",
113 | "idiom" : "ipad",
114 | "scale" : "2x",
115 | "size" : "50x50"
116 | },
117 | {
118 | "filename" : "72.png",
119 | "idiom" : "ipad",
120 | "scale" : "1x",
121 | "size" : "72x72"
122 | },
123 | {
124 | "filename" : "144.png",
125 | "idiom" : "ipad",
126 | "scale" : "2x",
127 | "size" : "72x72"
128 | },
129 | {
130 | "filename" : "76.png",
131 | "idiom" : "ipad",
132 | "scale" : "1x",
133 | "size" : "76x76"
134 | },
135 | {
136 | "filename" : "152.png",
137 | "idiom" : "ipad",
138 | "scale" : "2x",
139 | "size" : "76x76"
140 | },
141 | {
142 | "filename" : "167.png",
143 | "idiom" : "ipad",
144 | "scale" : "2x",
145 | "size" : "83.5x83.5"
146 | },
147 | {
148 | "filename" : "1024.png",
149 | "idiom" : "ios-marketing",
150 | "scale" : "1x",
151 | "size" : "1024x1024"
152 | },
153 | {
154 | "filename" : "16.png",
155 | "idiom" : "mac",
156 | "scale" : "1x",
157 | "size" : "16x16"
158 | },
159 | {
160 | "filename" : "32.png",
161 | "idiom" : "mac",
162 | "scale" : "2x",
163 | "size" : "16x16"
164 | },
165 | {
166 | "filename" : "32.png",
167 | "idiom" : "mac",
168 | "scale" : "1x",
169 | "size" : "32x32"
170 | },
171 | {
172 | "filename" : "64.png",
173 | "idiom" : "mac",
174 | "scale" : "2x",
175 | "size" : "32x32"
176 | },
177 | {
178 | "filename" : "128.png",
179 | "idiom" : "mac",
180 | "scale" : "1x",
181 | "size" : "128x128"
182 | },
183 | {
184 | "filename" : "256.png",
185 | "idiom" : "mac",
186 | "scale" : "2x",
187 | "size" : "128x128"
188 | },
189 | {
190 | "filename" : "256.png",
191 | "idiom" : "mac",
192 | "scale" : "1x",
193 | "size" : "256x256"
194 | },
195 | {
196 | "filename" : "512.png",
197 | "idiom" : "mac",
198 | "scale" : "2x",
199 | "size" : "256x256"
200 | },
201 | {
202 | "filename" : "512.png",
203 | "idiom" : "mac",
204 | "scale" : "1x",
205 | "size" : "512x512"
206 | },
207 | {
208 | "filename" : "1024.png",
209 | "idiom" : "mac",
210 | "scale" : "2x",
211 | "size" : "512x512"
212 | }
213 | ],
214 | "info" : {
215 | "author" : "xcode",
216 | "version" : 1
217 | }
218 | }
219 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Assets.xcassets/bg1.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0x7C",
9 | "green" : "0xFF",
10 | "red" : "0xB6"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Assets.xcassets/bg2.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0xE7",
9 | "green" : "0xDE",
10 | "red" : "0x54"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Assets.xcassets/bg3.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0xC0",
9 | "green" : "0xFF",
10 | "red" : "0x4F"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Assets.xcassets/bg4.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0xE1",
9 | "green" : "0xEC",
10 | "red" : "0x34"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Assets.xcassets/bg5.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0xF2",
9 | "green" : "0x6A",
10 | "red" : "0x23"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Assets.xcassets/deviceFrame.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0x00",
9 | "green" : "0x00",
10 | "red" : "0x00"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Assets.xcassets/gd1-1.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0xB3",
9 | "green" : "0xFF",
10 | "red" : "0x00"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Assets.xcassets/gd1-2.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0xD9",
9 | "green" : "0xE6",
10 | "red" : "0x00"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Assets.xcassets/gd1-3.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0xFF",
9 | "green" : "0xCC",
10 | "red" : "0x00"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Assets.xcassets/gd1-4.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0xFF",
9 | "green" : "0x8C",
10 | "red" : "0x00"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Assets.xcassets/gd1-5.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0xFF",
9 | "green" : "0x4C",
10 | "red" : "0x00"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Assets.xcassets/neutral100.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0xFA",
9 | "green" : "0xF9",
10 | "red" : "0xF8"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Assets.xcassets/neutral200.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0xEF",
9 | "green" : "0xEC",
10 | "red" : "0xE9"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Assets.xcassets/neutral300.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.855",
9 | "green" : "0.831",
10 | "red" : "0.808"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Assets.xcassets/neutral400.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.855",
9 | "green" : "0.831",
10 | "red" : "0.808"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Assets.xcassets/neutral500.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.490",
9 | "green" : "0.459",
10 | "red" : "0.424"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Assets.xcassets/neutral600.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.341",
9 | "green" : "0.314",
10 | "red" : "0.286"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Assets.xcassets/neutral700.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.251",
9 | "green" : "0.227",
10 | "red" : "0.204"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Assets.xcassets/neutral800.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.161",
9 | "green" : "0.145",
10 | "red" : "0.129"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Assets.xcassets/neutral900.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.071",
9 | "green" : "0.059",
10 | "red" : "0.051"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Assets.xcassets/neutralStrong.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.000",
9 | "green" : "0.000",
10 | "red" : "0.000"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Assets.xcassets/neutralWeak.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "1.000",
9 | "green" : "1.000",
10 | "red" : "1.000"
11 | }
12 | },
13 | "idiom" : "universal"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Assets.xcassets/shade1.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Screen Shot 2022-04-21 at 8.11.49 PM.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 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Assets.xcassets/shade1.imageset/Screen Shot 2022-04-21 at 8.11.49 PM.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ryendu/GradientDescend/c751956c75a9ee1a4b8338a4323f82296ab18c44/GradientDescend.swiftpm/Assets.xcassets/shade1.imageset/Screen Shot 2022-04-21 at 8.11.49 PM.png
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Assets.xcassets/wwdc.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "wwdc.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 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Assets.xcassets/wwdc.imageset/wwdc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ryendu/GradientDescend/c751956c75a9ee1a4b8338a4323f82296ab18c44/GradientDescend.swiftpm/Assets.xcassets/wwdc.imageset/wwdc.png
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version: 5.5
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: "GradientDescend",
12 | platforms: [
13 | .iOS("15.2")
14 | ],
15 | products: [
16 | .iOSApplication(
17 | name: "GradientDescend",
18 | targets: ["AppModule"],
19 | bundleIdentifier: "com.ryandu.gradient-descend",
20 | teamIdentifier: "95566VVRBB",
21 | displayVersion: "1.0",
22 | bundleVersion: "1",
23 | iconAssetName: "AppIcon",
24 | accentColorAssetName: "AccentColor",
25 | supportedDeviceFamilies: [
26 | .pad,
27 | .phone
28 | ],
29 | supportedInterfaceOrientations: [
30 | .landscapeRight,
31 | .landscapeLeft
32 | ]
33 | )
34 | ],
35 | targets: [
36 | .executableTarget(
37 | name: "AppModule",
38 | path: ".",
39 | resources: [
40 | .process("Resources/intro.m4a"),
41 | .process("Resources/simple3dgd.scn"),
42 |
43 | ]
44 | )
45 | ]
46 | )
47 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Packages/ConfettiSwiftUI/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Packages/ConfettiSwiftUI/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Packages/ConfettiSwiftUI/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Packages/ConfettiSwiftUI/.swiftpm/xcode/xcshareddata/xcschemes/ConfettiSwiftUI.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
38 |
39 |
44 |
45 |
47 |
53 |
54 |
55 |
56 |
57 |
67 |
68 |
74 |
75 |
81 |
82 |
83 |
84 |
86 |
87 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Packages/ConfettiSwiftUI/LICENSE_C:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Simon Bachmann
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Packages/ConfettiSwiftUI/README_C.md:
--------------------------------------------------------------------------------
1 | # ConfettiSwiftUI
2 |
3 |
4 |
5 | ### Customizable Confetti Animations in SwiftUI
6 |
7 |
8 |
9 |
10 |
11 | ## 🌄 Example
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | ## 🔭 Overview
21 |
22 | This is an open-source library to use with SwiftUI. It allows you to create and customize confetti animations.
23 |
24 | - Built with pure SwiftUI.
25 | - Select from default confetti shapes or inject emojis as text.
26 | - Configure the radius and angles of the explosion.
27 | - Trigger animation with one state change multiple times.
28 |
29 | ## 🔨Support
30 |
31 | If you like the project, don't forget to `put star 🌟`.
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | ## 🧭 Navigation
40 |
41 | - [💻 Installation](#-installation)
42 | - [Swift Package Manager](#swift-package-manager)
43 | - [Manually](#manually)
44 | - [🧳 Requirements](#-requirements)
45 | - [🛠 Usage](#-usage)
46 | - [Parameters](#parameters)
47 | - [Configurator Application With Live Preview](#configurator-application-with-live-preview)
48 | - [Examples](#examples)
49 | - [👨💻 Contributors](#-contributors)
50 | - [✍️ Author](#-author)
51 | - [📃 License](#-license)
52 | - [📦 Projects](#-projects)
53 |
54 | ## 💻 Installation
55 |
56 | ### Swift Package Manager
57 |
58 | The [Swift Package Manager](https://swift.org/package-manager/) is a tool for managing the distribution of Swift code. It’s integrated with the Swift build system to automate the process of downloading, compiling, and linking dependencies.
59 |
60 | To integrate `ConfettiSwiftUI` into your Xcode project using Xcode 12, specify it in `File > Swift Packages > Add Package Dependency...`:
61 |
62 | ```ogdl
63 | https://github.com/simibac/ConfettiSwiftUI.git, :branch="master"
64 | ```
65 |
66 | ---
67 |
68 | ### Manually
69 |
70 | If you prefer not to use any of dependency managers, you can integrate `ConfettiSwiftUI` into your project manually. Put `Sources/ConfettiSwiftUI` folder in your Xcode project. Make sure to enable `Copy items if needed` and `Create groups`.
71 |
72 | ## 🧳 Requirements
73 |
74 | - iOS 14.0+ | macOS 11+
75 | - Swift 5+
76 |
77 | ## 🛠 Usage
78 |
79 | First, add `import ConfettiSwiftUI` on every `swift` file you would like to use `ConfettiSwiftUI`. Define a integer as a state varable which is responsible for triggering the animation. Any change to that variable will span a new animation (increment and decrement).
80 |
81 | ```swift
82 |
83 | import SwiftUI
84 |
85 | struct ContentView: View {
86 |
87 | @State private var counter: Int = 0
88 |
89 | var body: some View {
90 | Button("🎉") {
91 | counter += 1
92 | }
93 | .confettiCannon(counter: $counter)
94 | }
95 | }
96 |
97 | ```
98 |
99 | ### Parameters
100 |
101 | | parameter | type | description | default |
102 | | ------------------ | -------------- | ----------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
103 | | counter | Binding | on any change of this variable triggers the animation | 0 |
104 | | num | Int | amount of confettis | 20 |
105 | | confettis | [ConfettiType] | list of shapes and text | [.shape(.circle), .shape(.triangle), .shape(.square), .shape(.slimRectangle), .shape(.roundedCross)] |
106 | | colors | [Color] | list of colors applied to the default shapes | [.blue, .red, .green, .yellow, .pink, .purple, .orange] |
107 | | confettiSize | CGFloat | size that confettis and emojis are scaled to | 10.0 |
108 | | rainHeight | CGFloat | vertical distance that confettis pass | 600.0 |
109 | | fadesOut | Bool | size that confettis and emojis are scaled to | true |
110 | | opacity | Double | maximum opacity during the animation | 1.0 |
111 | | openingAngle | Angle | boundary that defines the opening angle in degrees | Angle.degrees(60) |
112 | | closingAngle | Angle | boundary that defines the closing angle in degrees | Angle.degrees(120) |
113 | | radius | CGFloat | explosion radius | 300.0 |
114 | | repetitions | Int | number of repetitions for the explosion | 0 |
115 | | repetitionInterval | Double | duration between the repetitions | 1.0 |
116 |
117 | ### Configurator Application With Live Preview
118 |
119 | You can use the configurator app in [demo project here](https://github.com/simibac/ConfettiSwiftUIDemo) to make your desired animation or get inspired by one of the many examples.
120 |
121 |
122 |
123 |
124 |
125 |
126 | ### Examples
127 |
128 | #### Color and Size Configuration
129 |
130 |
131 |
132 |
133 |
134 | ```swift
135 | ConfettiCannon(counter: $counter2, colors: [.red, .black], confettiSize: 20)
136 | ```
137 |
138 | #### Repeat Configuration
139 |
140 |
141 |
142 |
143 |
144 | ```swift
145 | ConfettiCannon(counter: $counter3, repetitions: 3, repetitionInterval: 0.7)
146 | ```
147 |
148 | #### Firework Configuration
149 |
150 |
151 |
152 |
153 |
154 | ```swift
155 | ConfettiCannon(counter: $counter4, num: 50, openingAngle: Angle(degrees: 0), closingAngle: Angle(degrees: 360), radius: 200)
156 | ```
157 |
158 | #### Emoji Configuration
159 |
160 |
161 |
162 |
163 |
164 | ```swift
165 | ConfettiCannon(counter: $counter5, confettis: [.text("❤️"), .text("💙"), .text("💚"), .text("🧡")])
166 | ```
167 |
168 | #### Endless Configuration
169 |
170 |
171 |
172 |
173 |
174 | ```swift
175 | ConfettiCannon(counter: $counter6, num:1, confettis: [.text("💩")], confettiSize: 20, repetitions: 100, repetitionInterval: 0.1)
176 | ```
177 |
178 | #### Make-it-Rain Configuration
179 |
180 |
181 |
182 |
183 |
184 | ```swift
185 | ConfettiCannon(counter: $counter7, num:1, confettis: [.text("💵"), .text("💶"), .text("💷"), .text("💴")], confettiSize: 30, repetitions: 50, repetitionInterval: 0.1)
186 | ```
187 |
188 | ## 👨💻 Contributors
189 |
190 | All issue reports, feature requests, pull requests and GitHub stars are welcomed and much appreciated.
191 |
192 | ## ✍️ Author
193 |
194 | Simon Bachmann
195 |
196 | ## 📃 License
197 |
198 | `ConfettiSwiftUI` is available under the MIT license. See the [LICENSE](https://github.com/simibac/ConfettiSwiftUI/blob/master/LICENSE) file for more info.
199 |
200 | ## 📦 Projects
201 |
202 | The following projects have integrated ConfettiSwiftUI in their App.
203 |
204 | - [Basic Code](https://basiccode.de) available on the [AppStore](https://apps.apple.com/de/app/basiccode/id1562309250)
205 |
206 | ---
207 |
208 | - [Jump Up](#-overview)
209 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Packages/ConfettiSwiftUI/Sources/ConfettiSwiftUI.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ConfettiView.swift
3 | // Confetti
4 | //
5 | // Created by Simon Bachmann on 24.11.20.
6 | //
7 |
8 | import SwiftUI
9 |
10 | public enum ConfettiType:CaseIterable, Hashable {
11 |
12 | public enum Shape {
13 | case circle
14 | case triangle
15 | case square
16 | case slimRectangle
17 | case roundedCross
18 | }
19 |
20 | case shape(Shape)
21 | case text(String)
22 | case sfSymbol(symbolName: String)
23 |
24 | public var view:AnyView{
25 | switch self {
26 | case .shape(.square):
27 | return AnyView(Rectangle())
28 | case .shape(.triangle):
29 | return AnyView(Triangle())
30 | case .shape(.slimRectangle):
31 | return AnyView(SlimRectangle())
32 | case .shape(.roundedCross):
33 | return AnyView(RoundedCross())
34 | case let .text(text):
35 | return AnyView(Text(text))
36 | case .sfSymbol(let symbolName):
37 | return AnyView(Image(systemName: symbolName))
38 | default:
39 | return AnyView(Circle())
40 | }
41 | }
42 |
43 | public static var allCases: [ConfettiType] {
44 | return [.shape(.circle), .shape(.triangle), .shape(.square), .shape(.slimRectangle), .shape(.roundedCross)]
45 | }
46 | }
47 |
48 | @available(iOS 14.0, macOS 11.0, watchOS 7, tvOS 14.0, *)
49 | public struct ConfettiCannon: View {
50 | @Binding var counter:Int
51 | @StateObject private var confettiConfig:ConfettiConfig
52 |
53 | @State var animate:[Bool] = []
54 | @State var finishedAnimationCounter = 0
55 | @State var firstAppear = false
56 | @State var error = ""
57 |
58 | /// renders configurable confetti animaiton
59 | /// - Parameters:
60 | /// - counter: on any change of this variable the animation is run
61 | /// - num: amount of confettis
62 | /// - colors: list of colors that is applied to the default shapes
63 | /// - confettiSize: size that confettis and emojis are scaled to
64 | /// - rainHeight: vertical distance that confettis pass
65 | /// - fadesOut: reduce opacity towards the end of the animation
66 | /// - opacity: maximum opacity that is reached during the animation
67 | /// - openingAngle: boundary that defines the opening angle in degrees
68 | /// - closingAngle: boundary that defines the closing angle in degrees
69 | /// - radius: explosion radius
70 | /// - repetitions: number of repetitions of the explosion
71 | /// - repetitionInterval: duration between the repetitions
72 | public init(counter:Binding,
73 | num:Int = 20,
74 | confettis:[ConfettiType] = ConfettiType.allCases,
75 | colors:[Color] = [.blue, .red, .green, .yellow, .pink, .purple, .orange],
76 | confettiSize:CGFloat = 10.0,
77 | rainHeight: CGFloat = 600.0,
78 | fadesOut:Bool = true,
79 | opacity:Double = 1.0,
80 | openingAngle:Angle = .degrees(60),
81 | closingAngle:Angle = .degrees(120),
82 | radius:CGFloat = 300,
83 | repetitions:Int = 0,
84 | repetitionInterval:Double = 1.0
85 | ) {
86 | self._counter = counter
87 | var shapes = [AnyView]()
88 |
89 | for confetti in confettis{
90 | for color in colors{
91 | switch confetti {
92 | case .shape(_):
93 | shapes.append(AnyView(confetti.view.foregroundColor(color).frame(width: confettiSize, height: confettiSize, alignment: .center)))
94 | default:
95 | shapes.append(AnyView(confetti.view.foregroundColor(color).font(.system(size: confettiSize))))
96 | }
97 | }
98 | }
99 |
100 | _confettiConfig = StateObject(wrappedValue: ConfettiConfig(
101 | num: num,
102 | shapes: shapes,
103 | colors: colors,
104 | confettiSize: confettiSize,
105 | rainHeight: rainHeight,
106 | fadesOut: fadesOut,
107 | opacity: opacity,
108 | openingAngle: openingAngle,
109 | closingAngle: closingAngle,
110 | radius: radius,
111 | repetitions: repetitions,
112 | repetitionInterval: repetitionInterval
113 | ))
114 | }
115 |
116 | public var body: some View {
117 | ZStack{
118 | ForEach(finishedAnimationCounter.. AnyView {
173 | return confettiConfig.shapes.randomElement()!
174 | }
175 |
176 | func getColor() -> Color {
177 | return confettiConfig.colors.randomElement()!
178 | }
179 |
180 | func getSpinDirection() -> CGFloat {
181 | let spinDirections:[CGFloat] = [-1.0, 1.0]
182 | return spinDirections.randomElement()!
183 | }
184 |
185 | func getRandomExplosionTimeVariation() -> CGFloat {
186 | CGFloat((0...999).randomElement()!) / 2100
187 | }
188 |
189 | func getAnimationDuration() -> CGFloat {
190 | return 0.2 + confettiConfig.explosionAnimationDuration + getRandomExplosionTimeVariation()
191 | }
192 |
193 | func getAnimation() -> Animation {
194 | return Animation.timingCurve(0, 1, 0, 1, duration: getAnimationDuration())
195 | }
196 |
197 | func getDistance() -> CGFloat {
198 | return pow(CGFloat.random(in: 0.01...1), 2.0/7.0) * confettiConfig.radius
199 | }
200 |
201 | func getDelayBeforeRainAnimation() -> TimeInterval {
202 | confettiConfig.explosionAnimationDuration * 0.1
203 | }
204 |
205 | var body: some View{
206 | ConfettiAnimationView(shape:getShape(), color:getColor(), spinDirX: getSpinDirection(), spinDirZ: getSpinDirection())
207 | .offset(x: location.x, y: location.y)
208 | .opacity(opacity)
209 | .onAppear(){
210 | withAnimation(getAnimation()) {
211 | opacity = confettiConfig.opacity
212 |
213 | let randomAngle:CGFloat
214 | if confettiConfig.openingAngle.degrees <= confettiConfig.closingAngle.degrees{
215 | randomAngle = CGFloat.random(in: CGFloat(confettiConfig.openingAngle.degrees)...CGFloat(confettiConfig.closingAngle.degrees))
216 | }else{
217 | randomAngle = CGFloat.random(in: CGFloat(confettiConfig.openingAngle.degrees)...CGFloat(confettiConfig.closingAngle.degrees + 360)).truncatingRemainder(dividingBy: 360)
218 | }
219 |
220 | let distance = getDistance()
221 |
222 | location.x = distance * cos(deg2rad(randomAngle))
223 | location.y = -distance * sin(deg2rad(randomAngle))
224 | }
225 |
226 | DispatchQueue.main.asyncAfter(deadline: .now() + getDelayBeforeRainAnimation()) {
227 | withAnimation(Animation.timingCurve(0.12, 0, 0.39, 0, duration: confettiConfig.rainAnimationDuration)) {
228 | location.y += confettiConfig.rainHeight
229 | opacity = confettiConfig.fadesOut ? 0 : confettiConfig.opacity
230 | }
231 | }
232 | }
233 | }
234 |
235 | func deg2rad(_ number: CGFloat) -> CGFloat {
236 | return number * CGFloat.pi / 180
237 | }
238 |
239 | }
240 |
241 | struct ConfettiAnimationView: View {
242 | @State var shape: AnyView
243 | @State var color: Color
244 | @State var spinDirX: CGFloat
245 | @State var spinDirZ: CGFloat
246 | @State var firstAppear = true
247 |
248 |
249 | @State var move = false
250 | @State var xSpeed:Double = Double.random(in: 0.501...2.201)
251 |
252 | @State var zSpeed = Double.random(in: 0.501...2.201)
253 | @State var anchor = CGFloat.random(in: 0...1).rounded()
254 |
255 | var body: some View {
256 | shape
257 | .foregroundColor(color)
258 | .rotation3DEffect(.degrees(move ? 360:0), axis: (x: spinDirX, y: 0, z: 0))
259 | .animation(Animation.linear(duration: xSpeed).repeatCount(10, autoreverses: false), value: move)
260 | .rotation3DEffect(.degrees(move ? 360:0), axis: (x: 0, y: 0, z: spinDirZ), anchor: UnitPoint(x: anchor, y: anchor))
261 | .animation(Animation.linear(duration: zSpeed).repeatForever(autoreverses: false), value: move)
262 | .onAppear() {
263 | if firstAppear {
264 | move = true
265 | firstAppear = true
266 | }
267 | }
268 | }
269 | }
270 |
271 | class ConfettiConfig: ObservableObject {
272 | internal init(num: Int, shapes: [AnyView], colors: [Color], confettiSize: CGFloat, rainHeight: CGFloat, fadesOut: Bool, opacity: Double, openingAngle:Angle, closingAngle:Angle, radius:CGFloat, repetitions:Int, repetitionInterval:Double) {
273 | self.num = num
274 | self.shapes = shapes
275 | self.colors = colors
276 | self.confettiSize = confettiSize
277 | self.rainHeight = rainHeight
278 | self.fadesOut = fadesOut
279 | self.opacity = opacity
280 | self.openingAngle = openingAngle
281 | self.closingAngle = closingAngle
282 | self.radius = radius
283 | self.repetitions = repetitions
284 | self.repetitionInterval = repetitionInterval
285 | self.explosionAnimationDuration = Double(radius / 1400)
286 | self.rainAnimationDuration = Double((rainHeight + radius) / 200)
287 | }
288 |
289 | @Published var num:Int
290 | @Published var shapes:[AnyView]
291 | @Published var colors:[Color]
292 | @Published var confettiSize:CGFloat
293 | @Published var rainHeight:CGFloat
294 | @Published var fadesOut:Bool
295 | @Published var opacity:Double
296 | @Published var openingAngle:Angle
297 | @Published var closingAngle:Angle
298 | @Published var radius:CGFloat
299 | @Published var repetitions:Int
300 | @Published var repetitionInterval:Double
301 | @Published var explosionAnimationDuration:Double
302 | @Published var rainAnimationDuration:Double
303 |
304 |
305 | var animationDuration:Double{
306 | return explosionAnimationDuration + rainAnimationDuration
307 | }
308 |
309 | var openingAngleRad:CGFloat{
310 | return CGFloat(openingAngle.degrees) * 180 / .pi
311 | }
312 |
313 | var closingAngleRad:CGFloat{
314 | return CGFloat(closingAngle.degrees) * 180 / .pi
315 | }
316 | }
317 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Packages/ConfettiSwiftUI/Sources/Shapes/RoundedCross.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RoundedCross.swift
3 | // Confetti
4 | //
5 | // Created by Simon Bachmann on 04.12.20.
6 | //
7 |
8 | import SwiftUI
9 |
10 | public struct RoundedCross: Shape {
11 | public func path(in rect: CGRect) -> Path {
12 | var path = Path()
13 |
14 | path.move(to: CGPoint(x: rect.minX, y: rect.maxY/3))
15 | path.addQuadCurve(to: CGPoint(x: rect.maxX/3, y: rect.minY), control: CGPoint(x: rect.maxX/3, y: rect.maxY/3))
16 | path.addLine(to: CGPoint(x: 2*rect.maxX/3, y: rect.minY))
17 |
18 | path.addQuadCurve(to: CGPoint(x: rect.maxX, y: rect.maxY/3), control: CGPoint(x: 2*rect.maxX/3, y: rect.maxY/3))
19 | path.addLine(to: CGPoint(x: rect.maxX, y: 2*rect.maxY/3))
20 |
21 | path.addQuadCurve(to: CGPoint(x: 2*rect.maxX/3, y: rect.maxY), control: CGPoint(x: 2*rect.maxX/3, y: 2*rect.maxY/3))
22 | path.addLine(to: CGPoint(x: rect.maxX/3, y: rect.maxY))
23 |
24 | path.addQuadCurve(to: CGPoint(x: 2*rect.minX/3, y: 2*rect.maxY/3), control: CGPoint(x: rect.maxX/3, y: 2*rect.maxY/3))
25 |
26 | return path
27 | }
28 | }
29 |
30 | struct RoundedCross_Previews: PreviewProvider {
31 | static var previews: some View {
32 | RoundedCross()
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Packages/ConfettiSwiftUI/Sources/Shapes/SlimRectangle.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SlimRectangle.swift
3 | // Confetti
4 | //
5 | // Created by Simon Bachmann on 04.12.20.
6 | //
7 |
8 | import SwiftUI
9 |
10 | public struct SlimRectangle: Shape {
11 | public func path(in rect: CGRect) -> Path {
12 | var path = Path()
13 |
14 | path.move(to: CGPoint(x: rect.minX, y: 4*rect.maxY/5))
15 | path.addLine(to: CGPoint(x: rect.maxX, y: 4*rect.maxY/5))
16 | path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
17 | path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY))
18 |
19 | return path
20 | }
21 | }
22 |
23 | struct SlimRectangle_Previews: PreviewProvider {
24 | static var previews: some View {
25 | SlimRectangle()
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Packages/ConfettiSwiftUI/Sources/Shapes/Triangle.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Triangle.swift
3 | // Confetti
4 | //
5 | // Created by Simon Bachmann on 04.12.20.
6 | //
7 |
8 | import SwiftUI
9 |
10 | public struct Triangle: Shape {
11 | public func path(in rect: CGRect) -> Path {
12 | var path = Path()
13 |
14 | path.move(to: CGPoint(x: rect.midX, y: rect.minY))
15 | path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY))
16 | path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
17 | path.addLine(to: CGPoint(x: rect.midX, y: rect.minY))
18 |
19 | return path
20 | }
21 | }
22 |
23 | struct Triangle_Previews: PreviewProvider {
24 | static var previews: some View {
25 | Triangle()
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Packages/ConfettiSwiftUI/Sources/View+ConfettiCannon.swift:
--------------------------------------------------------------------------------
1 | //
2 | // View+ConfettiCannon.swift
3 | //
4 | //
5 | // Created by Abdullah Alhaider on 24/03/2022.
6 | //
7 |
8 | import SwiftUI
9 |
10 | public extension View {
11 |
12 | /// renders configurable confetti animaiton
13 | ///
14 | /// - Usage:
15 | ///
16 | /// ```
17 | /// import SwiftUI
18 | ///
19 | /// struct ContentView: View {
20 | ///
21 | /// @State private var counter: Int = 0
22 | ///
23 | /// var body: some View {
24 | /// Button("Wow") {
25 | /// counter += 1
26 | /// }
27 | /// .confettiCannon(counter: $counter)
28 | /// }
29 | /// }
30 | /// ```
31 | ///
32 | /// - Parameters:
33 | /// - counter: on any change of this variable the animation is run
34 | /// - num: amount of confettis
35 | /// - colors: list of colors that is applied to the default shapes
36 | /// - confettiSize: size that confettis and emojis are scaled to
37 | /// - rainHeight: vertical distance that confettis pass
38 | /// - fadesOut: reduce opacity towards the end of the animation
39 | /// - opacity: maximum opacity that is reached during the animation
40 | /// - openingAngle: boundary that defines the opening angle in degrees
41 | /// - closingAngle: boundary that defines the closing angle in degrees
42 | /// - radius: explosion radius
43 | /// - repetitions: number of repetitions of the explosion
44 | /// - repetitionInterval: duration between the repetitions
45 | ///
46 | @ViewBuilder func confettiCannon(
47 | counter: Binding,
48 | num: Int = 20,
49 | confettis: [ConfettiType] = ConfettiType.allCases,
50 | colors: [Color] = [.blue, .red, .green, .yellow, .pink, .purple, .orange],
51 | confettiSize: CGFloat = 10.0,
52 | rainHeight: CGFloat = 600.0,
53 | fadesOut: Bool = true,
54 | opacity: Double = 1.0,
55 | openingAngle: Angle = .degrees(60),
56 | closingAngle: Angle = .degrees(120),
57 | radius: CGFloat = 300,
58 | repetitions: Int = 0,
59 | repetitionInterval: Double = 1.0
60 | ) -> some View {
61 | ZStack {
62 | self
63 | ConfettiCannon(
64 | counter: counter,
65 | num: num,
66 | confettis: confettis,
67 | colors: colors,
68 | confettiSize: confettiSize,
69 | rainHeight: rainHeight,
70 | fadesOut: fadesOut,
71 | opacity: opacity,
72 | openingAngle: openingAngle,
73 | closingAngle: closingAngle,
74 | radius: radius,
75 | repetitions: repetitions,
76 | repetitionInterval: repetitionInterval
77 | )
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Packages/IrregularGradient/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Packages/IrregularGradient/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Packages/IrregularGradient/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Packages/IrregularGradient/LICENSE_I:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 João Gabriel
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Packages/IrregularGradient/README_I.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | A SwiftUI library for rendering beautiful, animated and _irregular_ gradient views.
11 |
12 | ## Installation
13 |
14 | Since this is a Swift package, it can be installed via the Swift Package Manager. To do this, all you gotta do is open your Xcode project, add a new depedency under **File > Swift Package Manager**, search for this repo and install it. Then, it's all done!
15 |
16 | ## Usage
17 |
18 | To use this library all you gotta do is add `import IrregularGradient` to the file you're using and then add an irregular gradient to any view you want with the `irregularGradient(colors: [Color], background: () -> View, shouldAnimate: Binding = .constant(true), speed: Double = 10)` modifier.
19 |
20 | - The first argument (and the only required one) `colors` specifies the colors of each gradient blob. Order and quantity matters, so the last color you add will always stay on top, and having two entries of the same color will create two distinct blue blobs.
21 | - The `background` argument defines the background of your gradient. It's a closure that returns a view, but can also just be a simple color if you use `backgroundColor` instead. Not specifying this value it will make the background transparent.
22 | - `shouldAnimate` is a boolean binding that specifies wheter or not the gradient should perform its natural movement. It can be enabled and disabled dinamically, but movement will never stop abruptly.
23 | - The `speed` argument accepts a Double and defines how long it takes for the blobs to move and update. The smaller the value, the faster the movement.
24 |
25 | ```swift
26 | RoundedRectangle(cornerRadius: 30.0, style: .continuous)
27 | .irregularGradient(colors: [.orange, .pink, .yellow, .orange, .pink, .yellow], backgroundColor: .orange)
28 | ```
29 |
30 | You can also use the `IrregularGradient` view which works the exact same way except for the fact that instead of filling an already existing view, it exists in its own container.
31 |
32 | ### Questions
33 |
34 | If you have any questions or suggestions, you can create an issue or pull request on this GitHub repository or even contact me via [Twitter](https://twitter.com/joogps) or [email](mailto:joogps@gmail.com).
35 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Packages/IrregularGradient/Sources/IrregularGradient/Blob.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Blob.swift
3 | //
4 | //
5 | // Created by Julian Schiavo on 5/4/2021.
6 | //
7 |
8 | import Combine
9 | import SwiftUI
10 |
11 | struct Blob: View {
12 | var color: Color
13 | var shouldAnimate: Bool
14 | var speed: Double
15 | var geometry: GeometryProxy
16 |
17 | @State private var position: CGPoint = CGPoint(x: CGFloat.random(in: 0...1), y: CGFloat.random(in: 0...1))
18 | @State private var scale: CGSize = CGSize(width: CGFloat.random(in: 0...1), height: CGFloat.random(in: 0...1))
19 |
20 | private let timer: Publishers.Autoconnect
21 |
22 | init(color: Color, animate: Bool, speed: Double, geometry: GeometryProxy) {
23 | self.color = color
24 | self.shouldAnimate = animate
25 | self.speed = speed
26 | self.geometry = geometry
27 |
28 | let interval = speed / Double.random(in: 2...5)
29 | self.timer = Timer.publish(every: interval, on: .main, in: .common).autoconnect()
30 | }
31 |
32 | private var animation: Animation {
33 | .spring(response: speed * Double.random(in: 0.8...1.25))
34 | }
35 |
36 | private var transformedPosition: CGPoint {
37 | let transform = CGAffineTransform(scaleX: geometry.size.width, y: geometry.size.height)
38 | return position.applying(transform)
39 | }
40 |
41 | var body: some View {
42 | Ellipse()
43 | .fill(color)
44 | .position(transformedPosition)
45 | .scaleEffect(scale)
46 | .animation(animation)
47 | .onAppear(perform: update)
48 | .onReceive(timer) { _ in
49 | update()
50 | }
51 | }
52 |
53 | private func update() {
54 | guard shouldAnimate else { return }
55 | position = CGPoint(x: CGFloat.random(in: 0...1), y: CGFloat.random(in: 0...1))
56 | scale = CGSize(width: CGFloat.random(in: 0...2), height: CGFloat.random(in: 0...2))
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Packages/IrregularGradient/Sources/IrregularGradient/IrregularGradient.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IrregularGradient.swift
3 | //
4 | //
5 | // Created by João Gabriel Pozzobon dos Santos on 01/12/20.
6 | //
7 |
8 | import SwiftUI
9 | import Combine
10 |
11 | public struct IrregularGradient: View {
12 | var colors: [Color]
13 | var background: Background
14 | var speed: Double
15 | var shouldAnimate: Binding
16 |
17 | public init(colors: [Color], background: @autoclosure @escaping () -> Background, speed: Double = 10, shouldAnimate: Binding = .constant(true)) {
18 | self.colors = colors
19 | self.background = background()
20 | self.shouldAnimate = shouldAnimate
21 | self.speed = speed
22 | }
23 |
24 | public var body: some View {
25 | GeometryReader { geometry in
26 | ZStack {
27 | background
28 | ZStack {
29 | ForEach(0.. = .constant(true)) {
42 | self.colors = colors
43 | self.background = backgroundColor
44 | self.shouldAnimate = shouldAnimate
45 | self.speed = speed
46 | }
47 | }
48 |
49 | struct IrregularGradient_Previews: PreviewProvider {
50 | static var previews: some View {
51 | PreviewWrapper()
52 | }
53 |
54 | struct PreviewWrapper: View {
55 | @State var animate = true
56 |
57 | var body: some View {
58 | VStack {
59 | RoundedRectangle(cornerRadius: 30.0, style: .continuous)
60 | .irregularGradient(colors: [.orange, .pink, .yellow, .orange, .pink, .yellow], backgroundColor: .orange, shouldAnimate: $animate)
61 | Toggle("Animate", isOn: $animate)
62 | .padding()
63 | }
64 | .padding(25)
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Packages/IrregularGradient/Sources/IrregularGradient/IrregularGradientView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IrregularGradientView.swift
3 | //
4 | //
5 | // Created by Julian Schiavo on 5/4/2021.
6 | //
7 |
8 | import SwiftUI
9 |
10 | @available(*, deprecated, renamed: "IrregularGradient")
11 | public struct IrregularGradientView: View {
12 | var colors: [Color]
13 | var backgroundColor: Color
14 | var animate: Binding
15 | var speed: Double
16 |
17 | public init(colors: [Color], backgroundColor: Color = .clear, animate: Binding = .constant(true), speed: Double = 10) {
18 | self.colors = colors
19 | self.backgroundColor = backgroundColor
20 | self.animate = animate
21 | self.speed = speed
22 | }
23 |
24 | public var body: some View {
25 | GeometryReader { geometry in
26 | ZStack {
27 | backgroundColor
28 | ZStack {
29 | ForEach(0..(colors: [Color], background: @autoclosure @escaping () -> Background, shouldAnimate: Binding = .constant(true), speed: Double = 10) -> some View {
12 | self
13 | .overlay(IrregularGradient(colors: colors, background: background(), speed: speed, shouldAnimate: shouldAnimate))
14 | .mask(self)
15 | }
16 |
17 | public func irregularGradient(colors: [Color], backgroundColor: Color = .clear, shouldAnimate: Binding = .constant(true), speed: Double = 10) -> some View {
18 | self
19 | .overlay(IrregularGradient(colors: colors, backgroundColor: backgroundColor, speed: speed, shouldAnimate: shouldAnimate))
20 | .mask(self)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Resources/intro.m4a:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ryendu/GradientDescend/c751956c75a9ee1a4b8338a4323f82296ab18c44/GradientDescend.swiftpm/Resources/intro.m4a
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Resources/simple3dgd.scn:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ryendu/GradientDescend/c751956c75a9ee1a4b8338a4323f82296ab18c44/GradientDescend.swiftpm/Resources/simple3dgd.scn
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Views/2D/2DGraph.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Ryan D on 4/7/22.
6 | //
7 |
8 | import Foundation
9 | import SwiftUI
10 |
11 | struct SimpleInteractiveGraph2DView: View {
12 | @Binding var cardIndex: Int
13 | @Binding var xValue: Double
14 | var body: some View {
15 | ZStack {
16 | Grid(cardIndex: $cardIndex, config: GridConfig(xStepSize: 1, yStepSize: 1, xTotal: 10, yTotal: 10, yLabel: "Error", xLabel: "Model Parameter"), content: { help in
17 | ZStack {
18 | Path { path in
19 | //graphing x^2
20 | path.move(to: CGPoint(x: 0, y: 0))
21 | path.addCurve(to: CGPoint(x: help.geo.size.width, y: 0), control1: CGPoint(x: help.geo.size.width / 4, y: help.geo.size.height * 1.25), control2: CGPoint(x: help.geo.size.width - help.geo.size.width / 4, y: help.geo.size.height * 1.25))
22 |
23 |
24 |
25 |
26 | }.trim(from: 0, to: self.cardIndex > 0 ? 1 : 0)
27 |
28 | .stroke(Color("bg5"), lineWidth: 3)
29 |
30 | .animation(.easeIn(duration: 2), value: self.cardIndex)
31 |
32 | getStepPath(help: help)
33 | .trim(from: 0, to: self.cardIndex > 5 ? 1 : 0)
34 | .stroke(.purple, lineWidth: 3)
35 | .animation(.easeIn(duration: 3), value: self.cardIndex)
36 |
37 |
38 | Circle()
39 | .frame(width: 25, height: 25)
40 | .irregularGradient(colors: [Color("bg5"),Color("bg3"),Color("bg4")], backgroundColor: Color("bg4"))
41 | .opacity(self.cardIndex > 1 ? 1 : 0)
42 | .animation(.default, value: self.cardIndex)
43 | .position(x: xValue * help.xScale, y: yOf(xValue * help.xScale, help: help))
44 |
45 |
46 | }
47 | .onChange(of: cardIndex, perform: {a in
48 | if a == 6 {
49 | self.xValue = 0
50 | } else if a == 7 {
51 | DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
52 | self.xValue = 5
53 | }
54 |
55 | }
56 | })
57 | })
58 | }
59 | }
60 | func getStepPath(help: GridInfo) -> Path{
61 | // step size
62 | let stepSize = 1.0
63 | let steps = 5
64 |
65 | var path = Path()
66 |
67 | for i in 0.. CGFloat {
80 | // p0 and p2 are end points of line
81 | // p1 is the control point
82 | // adapted from https://stackoverflow.com/a/16756481/13770657
83 | // equation for getting y point of an x point on a beizer cubic curve
84 | let control1 = CGPoint(x: help.geo.size.width / 4, y: help.geo.size.height * 1.25)
85 | let control2 = CGPoint(x: help.geo.size.width - help.geo.size.width / 4, y: help.geo.size.height * 1.25)
86 | let start = CGPoint(x: 0, y: 0)
87 | let end = CGPoint(x: 10 * help.xScale, y: 0)
88 |
89 | let T = x / help.geo.size.width
90 | let y0 = start.y
91 | let y1 = control1.y
92 | let y2 = control2.y
93 | let y3 = end.y
94 |
95 | let y_1 = (1-T) * (1-T) * (1-T) * y0
96 | let y_2 = 3 * T * (1-T) * (1-T) * y1
97 | let y_3 = 3 * T * T * (1-T)
98 | let y_4 = y2 + T * T * T * y3
99 |
100 | let y = y_1 + y_2 + y_3 * y_4
101 |
102 | return y
103 | }
104 | }
105 |
106 | struct ComplexInteractiveGraph2DView: View {
107 | @Binding var cardIndex: Int
108 | @Binding var xValue: Double
109 | @Binding var learningRateIndx: Double
110 | @State var circlePosition = CGPoint.zero
111 | var body: some View {
112 | ZStack {
113 | Grid(cardIndex: $cardIndex, config: GridConfig(xStepSize: 1, yStepSize: 1, xTotal: 10, yTotal: 10, yLabel: "Cost Function", xLabel: "Model Parameter"), content: { help in
114 | ZStack {
115 |
116 |
117 | complexFunctionPath(rect: CGRect(x: 0, y: help.yScale, width: help.geo.size.width, height: help.geo.size.height))
118 | .trim(from: 0, to: self.cardIndex > 0 ? 1 : 0)
119 | .stroke(Color("bg5"), lineWidth: 3)
120 | .animation(.easeIn(duration: 2), value: self.cardIndex)
121 | .offset(x: 0, y: help.yScale)
122 |
123 |
124 | gradientDescentPath1(rect: CGRect(x: 0, y: 0, width: help.geo.size.width, height: help.geo.size.height))
125 | .trim(from: 0, to: self.cardIndex > 1 && self.cardIndex < 3 ? 1 : 0)
126 | .stroke(Color.accentColor, lineWidth: 3)
127 | .animation(.easeIn(duration: 2.5), value: self.cardIndex)
128 | .offset(x: 0, y: help.yScale * 2)
129 |
130 | gradientDescentLR_0001(rect: CGRect(x: 0, y: 0, width: help.geo.size.width, height: help.geo.size.height))
131 | .trim(from: 0, to: self.cardIndex > 2 && self.cardIndex < 5 && learningRateIndx == 1.0 ? 1 : 0)
132 | .stroke(Color.yellow, lineWidth: 3)
133 | .animation(.easeIn(duration: 2.5), value: self.cardIndex)
134 | .offset(x: 0, y: help.yScale * 2)
135 |
136 | gradientDescentLR_0005(rect: CGRect(x: 0, y: 0, width: help.geo.size.width, height: help.geo.size.height))
137 | .trim(from: 0, to: self.cardIndex > 2 && self.cardIndex < 5 && learningRateIndx == 2.0 ? 1 : 0)
138 | .stroke(Color.orange, lineWidth: 3)
139 | .animation(.easeIn(duration: 2.5), value: self.cardIndex)
140 | .offset(x: 0, y: help.yScale * 2)
141 |
142 | gradientDescentLR_001(rect: CGRect(x: 0, y: 0, width: help.geo.size.width, height: help.geo.size.height))
143 | .trim(from: 0, to: self.cardIndex > 2 && self.cardIndex < 5 && learningRateIndx == 3.0 ? 1 : 0)
144 | .stroke(Color.red, lineWidth: 3)
145 | .animation(.easeIn(duration: 2.5), value: self.cardIndex)
146 | .offset(x: 0, y: help.yScale * 2)
147 |
148 | gradientDescentLR_01(rect: CGRect(x: 0, y: 0, width: help.geo.size.width, height: help.geo.size.height))
149 | .trim(from: 0, to: self.cardIndex > 2 && self.cardIndex < 5 && learningRateIndx == 4.0 ? 1 : 0)
150 | .stroke(Color.pink, lineWidth: 3)
151 | .animation(.easeIn(duration: 2.5), value: self.cardIndex)
152 | .offset(x: 0, y: help.yScale * 2)
153 |
154 | gradientDescentLR_1(rect: CGRect(x: 0, y: 0, width: help.geo.size.width, height: help.geo.size.height))
155 | .trim(from: 0, to: self.cardIndex > 2 && self.cardIndex < 5 && learningRateIndx == 5.0 ? 1 : 0)
156 | .stroke(Color.green, lineWidth: 3)
157 | .animation(.easeIn(duration: 2.5), value: self.cardIndex)
158 | .offset(x: 0, y: help.yScale * 2)
159 |
160 | Circle()
161 | .frame(width: 25, height: 25)
162 | .irregularGradient(colors: [Color("bg5"),Color("bg3"),Color("bg4")], backgroundColor: Color("bg4"))
163 | .opacity(self.cardIndex > 1 && self.cardIndex < 3 ? 1 : 0)
164 | .animation(.default, value: self.cardIndex)
165 | .position(x: circlePosition.x, y: circlePosition.y)
166 |
167 | }
168 | .onAppear {
169 | circlePosition = CGPoint(x: 0, y: help.yScale * 2)
170 | }
171 | .onChange(of: cardIndex, perform: {a in
172 | if a == 1 {
173 | DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
174 | self.circlePosition = CGPoint(x: 2 * help.xScale, y: 5 * help.yScale)
175 | }
176 | }
177 | })
178 | })
179 | }
180 | }
181 | func complexFunctionPath(rect: CGRect) -> Path {
182 | var path = Path()
183 | let width = rect.size.width
184 | let height = rect.size.height
185 | path.move(to: CGPoint(x: 0.00458*width, y: 0.10345*height))
186 | path.addCurve(to: CGPoint(x: 0.29977*width, y: 0.33699*height), control1: CGPoint(x: 0.03585*width, y: 0.23616*height), control2: CGPoint(x: 0.15203*width, y: 0.53135*height))
187 | path.addCurve(to: CGPoint(x: 0.79748*width, y: 0.33699*height), control1: CGPoint(x: 0.46933*width, y: 0.11393*height), control2: CGPoint(x: 0.51165*width, y: 1.0337*height))
188 | path.addCurve(to: CGPoint(x: 1.00114*width, y: 0.00313*height), control1: CGPoint(x: 0.85278*width, y: 0.20219*height), control2: CGPoint(x: 0.93936*width, y: -0.00235*height))
189 | return path
190 | }
191 |
192 | func gradientDescentPath1(rect: CGRect) -> Path {
193 | var path = Path()
194 | let width = rect.size.width
195 | let height = rect.size.height
196 | path.move(to: CGPoint(x: 0.00343*width, y: 0.00157*height))
197 | path.addCurve(to: CGPoint(x: 0.04691*width, y: 0.12853*height), control1: CGPoint(x: 0.03242*width, y: 0.01019*height), control2: CGPoint(x: 0.08169*width, y: 0.04765*height))
198 | path.addCurve(to: CGPoint(x: 0.09153*width, y: 0.21787*height), control1: CGPoint(x: 0.07666*width, y: 0.13427*height), control2: CGPoint(x: 0.12723*width, y: 0.16019*height))
199 | path.addCurve(to: CGPoint(x: 0.12357*width, y: 0.25627*height), control1: CGPoint(x: 0.11289*width, y: 0.21447*height), control2: CGPoint(x: 0.1492*width, y: 0.2174*height))
200 | path.addCurve(to: CGPoint(x: 0.15217*width, y: 0.2837*height), control1: CGPoint(x: 0.14035*width, y: 0.25418*height), control2: CGPoint(x: 0.17231*width, y: 0.25549*height))
201 | path.addCurve(to: CGPoint(x: 0.18879*width, y: 0.29859*height), control1: CGPoint(x: 0.16629*width, y: 0.27952*height), control2: CGPoint(x: 0.19336*width, y: 0.27665*height))
202 | return path
203 | }
204 |
205 | func gradientDescentLR_0001(rect: CGRect) -> Path {
206 | var path = Path()
207 | let width = rect.size.width
208 | let height = rect.size.height
209 | path.move(to: CGPoint(x: 0.00229*width, y: 0.00157*height))
210 | path.addCurve(to: CGPoint(x: 0.02517*width, y: 0.0815*height), control1: CGPoint(x: 0.03166*width, y: 0.01358*height), control2: CGPoint(x: 0.07735*width, y: 0.04639*height))
211 | path.addCurve(to: CGPoint(x: 0.04577*width, y: 0.13245*height), control1: CGPoint(x: 0.04996*width, y: 0.08333*height), control2: CGPoint(x: 0.08879*width, y: 0.09608*height))
212 | path.addCurve(to: CGPoint(x: 0.06064*width, y: 0.16458*height), control1: CGPoint(x: 0.0656*width, y: 0.13218*height), control2: CGPoint(x: 0.09634*width, y: 0.13824*height))
213 | path.addCurve(to: CGPoint(x: 0.07437*width, y: 0.19044*height), control1: CGPoint(x: 0.07857*width, y: 0.16432*height), control2: CGPoint(x: 0.10641*width, y: 0.16912*height))
214 | path.addCurve(to: CGPoint(x: 0.08352*width, y: 0.20846*height), control1: CGPoint(x: 0.09001*width, y: 0.18757*height), control2: CGPoint(x: 0.11373*width, y: 0.18715*height))
215 | path.addCurve(to: CGPoint(x: 0.09497*width, y: 0.22414*height), control1: CGPoint(x: 0.09878*width, y: 0.20585*height), control2: CGPoint(x: 0.12243*width, y: 0.20533*height))
216 | path.addCurve(to: CGPoint(x: 0.10526*width, y: 0.23824*height), control1: CGPoint(x: 0.10679*width, y: 0.22257*height), control2: CGPoint(x: 0.1254*width, y: 0.2232*height))
217 | path.addCurve(to: CGPoint(x: 0.11556*width, y: 0.25078*height), control1: CGPoint(x: 0.11556*width, y: 0.23772*height), control2: CGPoint(x: 0.13204*width, y: 0.2395*height))
218 | path.addCurve(to: CGPoint(x: 0.12243*width, y: 0.2594*height), control1: CGPoint(x: 0.12357*width, y: 0.24974*height), control2: CGPoint(x: 0.13616*width, y: 0.25*height))
219 | path.addCurve(to: CGPoint(x: 0.13043*width, y: 0.26881*height), control1: CGPoint(x: 0.13005*width, y: 0.25888*height), control2: CGPoint(x: 0.14233*width, y: 0.26003*height))
220 | path.addCurve(to: CGPoint(x: 0.14073*width, y: 0.27743*height), control1: CGPoint(x: 0.13844*width, y: 0.26829*height), control2: CGPoint(x: 0.15172*width, y: 0.26928*height))
221 | path.addCurve(to: CGPoint(x: 0.15103*width, y: 0.28448*height), control1: CGPoint(x: 0.1476*width, y: 0.27691*height), control2: CGPoint(x: 0.15927*width, y: 0.27759*height))
222 | path.addCurve(to: CGPoint(x: 0.16133*width, y: 0.28997*height), control1: CGPoint(x: 0.15637*width, y: 0.28292*height), control2: CGPoint(x: 0.1659*width, y: 0.28182*height))
223 | path.addCurve(to: CGPoint(x: 0.17277*width, y: 0.29467*height), control1: CGPoint(x: 0.16743*width, y: 0.2884*height), control2: CGPoint(x: 0.17826*width, y: 0.28715*height))
224 | path.addCurve(to: CGPoint(x: 0.18192*width, y: 0.29781*height), control1: CGPoint(x: 0.17696*width, y: 0.29258*height), control2: CGPoint(x: 0.18467*width, y: 0.29028*height))
225 | path.addCurve(to: CGPoint(x: 0.19222*width, y: 0.30016*height), control1: CGPoint(x: 0.18535*width, y: 0.29598*height), control2: CGPoint(x: 0.19222*width, y: 0.29389*height))
226 | path.addCurve(to: CGPoint(x: 0.20137*width, y: 0.30016*height), control1: CGPoint(x: 0.19451*width, y: 0.29754*height), control2: CGPoint(x: 0.19954*width, y: 0.29389*height))
227 | return path
228 | }
229 |
230 | func gradientDescentLR_0005(rect: CGRect) -> Path {
231 | var path = Path()
232 | let width = rect.size.width
233 | let height = rect.size.height
234 | path.move(to: CGPoint(x: 0.00343*width, y: 0.00157*height))
235 | path.addCurve(to: CGPoint(x: 0.04691*width, y: 0.12853*height), control1: CGPoint(x: 0.03242*width, y: 0.01019*height), control2: CGPoint(x: 0.08169*width, y: 0.04765*height))
236 | path.addCurve(to: CGPoint(x: 0.09153*width, y: 0.21787*height), control1: CGPoint(x: 0.07666*width, y: 0.13427*height), control2: CGPoint(x: 0.12723*width, y: 0.16019*height))
237 | path.addCurve(to: CGPoint(x: 0.12357*width, y: 0.25627*height), control1: CGPoint(x: 0.11289*width, y: 0.21447*height), control2: CGPoint(x: 0.1492*width, y: 0.2174*height))
238 | path.addCurve(to: CGPoint(x: 0.15217*width, y: 0.2837*height), control1: CGPoint(x: 0.14035*width, y: 0.25418*height), control2: CGPoint(x: 0.17231*width, y: 0.25549*height))
239 | path.addCurve(to: CGPoint(x: 0.18879*width, y: 0.29859*height), control1: CGPoint(x: 0.16629*width, y: 0.27952*height), control2: CGPoint(x: 0.19336*width, y: 0.27665*height))
240 | return path
241 | }
242 |
243 | func gradientDescentLR_001(rect: CGRect) -> Path {
244 | var path = Path()
245 | let width = rect.size.width
246 | let height = rect.size.height
247 | path.move(to: CGPoint(x: 0.00229*width, y: 0.00235*height))
248 | path.addCurve(to: CGPoint(x: 0.05492*width, y: 0.15596*height), control1: CGPoint(x: 0.04805*width, y: 0.02247*height), control2: CGPoint(x: 0.12265*width, y: 0.08135*height))
249 | path.addCurve(to: CGPoint(x: 0.11556*width, y: 0.25*height), control1: CGPoint(x: 0.08734*width, y: 0.157*height), control2: CGPoint(x: 0.16041*width, y: 0.19295*height))
250 | path.addCurve(to: CGPoint(x: 0.15904*width, y: 0.28997*height), control1: CGPoint(x: 0.14493*width, y: 0.2466*height), control2: CGPoint(x: 0.17391*width, y: 0.25*height))
251 | path.addCurve(to: CGPoint(x: 0.19908*width, y: 0.29859*height), control1: CGPoint(x: 0.17124*width, y: 0.28135*height), control2: CGPoint(x: 0.19634*width, y: 0.271*height))
252 | return path
253 | }
254 |
255 | func gradientDescentLR_01(rect: CGRect) -> Path {
256 | var path = Path()
257 | let width = rect.size.width
258 | let height = rect.size.height
259 | path.move(to: CGPoint(x: 0.00343*width, y: 0.00235*height))
260 | path.addCurve(to: CGPoint(x: 0.74943*width, y: 0.33386*height), control1: CGPoint(x: 0.26049*width, y: 0.00078*height), control2: CGPoint(x: 0.76957*width, y: 0.06489*height))
261 | path.addCurve(to: CGPoint(x: 0.41076*width, y: 0.24216*height), control1: CGPoint(x: 0.72121*width, y: 0.27769*height), control2: CGPoint(x: 0.61396*width, y: 0.18072*height))
262 | path.addCurve(to: CGPoint(x: 0.71968*width, y: 0.38401*height), control1: CGPoint(x: 0.50725*width, y: 0.24895*height), control2: CGPoint(x: 0.70412*width, y: 0.28683*height))
263 | path.addCurve(to: CGPoint(x: 0.49542*width, y: 0.39655*height), control1: CGPoint(x: 0.68459*width, y: 0.34875*height), control2: CGPoint(x: 0.59062*width, y: 0.30188*height))
264 | path.addCurve(to: CGPoint(x: 0.67735*width, y: 0.44984*height), control1: CGPoint(x: 0.55416*width, y: 0.38819*height), control2: CGPoint(x: 0.67277*width, y: 0.38715*height))
265 | path.addCurve(to: CGPoint(x: 0.56407*width, y: 0.48589*height), control1: CGPoint(x: 0.64684*width, y: 0.43939*height), control2: CGPoint(x: 0.58146*width, y: 0.43197*height))
266 | return path
267 | }
268 |
269 | func gradientDescentLR_1(rect: CGRect) -> Path {
270 | var path = Path()
271 | let width = rect.size.width
272 | let height = rect.size.height
273 | path.move(to: CGPoint(x: 0.00343*width, y: 0.13323*height))
274 | path.addCurve(to: CGPoint(x: 0.93936*width, y: 0.06975*height), control1: CGPoint(x: 0.13654*width, y: 0.05068*height), control2: CGPoint(x: 0.51007*width, y: -0.07759*height))
275 | path.addCurve(to: CGPoint(x: 0.03318*width, y: 0.23041*height), control1: CGPoint(x: 0.71625*width, y: 0.03918*height), control2: CGPoint(x: 0.22265*width, y: 0.02853*height))
276 | path.addCurve(to: CGPoint(x: 0.81465*width, y: 0.30486*height), control1: CGPoint(x: 0.22922*width, y: 0.17085*height), control2: CGPoint(x: 0.65995*width, y: 0.10235*height))
277 | path.addCurve(to: CGPoint(x: 0.127*width, y: 0.38793*height), control1: CGPoint(x: 0.65522*width, y: 0.2662*height), control2: CGPoint(x: 0.29451*width, y: 0.22868*height))
278 | path.addCurve(to: CGPoint(x: 0.85584*width, y: 0.22179*height), control1: CGPoint(x: 0.22731*width, y: 0.24843*height), control2: CGPoint(x: 0.5135*width, y: 0.01991*height))
279 | path.addCurve(to: CGPoint(x: 0.42792*width, y: 0.3989*height), control1: CGPoint(x: 0.754*width, y: 0.21891*height), control2: CGPoint(x: 0.52586*width, y: 0.25031*height))
280 | path.addCurve(to: CGPoint(x: 0.74943*width, y: 0.45611*height), control1: CGPoint(x: 0.53509*width, y: 0.34613*height), control2: CGPoint(x: 0.74943*width, y: 0.2837*height))
281 | path.addCurve(to: CGPoint(x: 0.3524*width, y: 0.32994*height), control1: CGPoint(x: 0.71014*width, y: 0.3861*height), control2: CGPoint(x: 0.57574*width, y: 0.26285*height))
282 | path.addCurve(to: CGPoint(x: 0.83982*width, y: 0.25549*height), control1: CGPoint(x: 0.38291*width, y: 0.26541*height), control2: CGPoint(x: 0.52311*width, y: 0.16019*height))
283 | path.addCurve(to: CGPoint(x: 0.46911*width, y: 0.48119*height), control1: CGPoint(x: 0.76316*width, y: 0.25575*height), control2: CGPoint(x: 0.58169*width, y: 0.30125*height))
284 | return path
285 | }
286 |
287 | func yOf(_ x: CGFloat, help: GridInfo) -> CGFloat {
288 | // p0 and p2 are end points of line
289 | // p1 is the control point
290 | // adapted from https://stackoverflow.com/a/16756481/13770657
291 | // equation for getting y point of an x point on a bezier cubic curve
292 | let control1 = CGPoint(x: help.geo.size.width / 4, y: help.geo.size.height * 1.25)
293 | let control2 = CGPoint(x: help.geo.size.width - help.geo.size.width / 4, y: help.geo.size.height * 1.25)
294 | let start = CGPoint(x: 0, y: 0)
295 | let end = CGPoint(x: 10 * help.xScale, y: 0)
296 |
297 | let T = x / help.geo.size.width
298 | let y0 = start.y
299 | let y1 = control1.y
300 | let y2 = control2.y
301 | let y3 = end.y
302 |
303 | let y_1 = (1-T) * (1-T) * (1-T) * y0
304 | let y_2 = 3 * T * (1-T) * (1-T) * y1
305 | let y_3 = 3 * T * T * (1-T)
306 | let y_4 = y2 + T * T * T * y3
307 |
308 | let y = y_1 + y_2 + y_3 * y_4
309 |
310 | return y
311 | }
312 | }
313 |
314 | struct GridConfig: Hashable {
315 | var xStepSize: Double
316 | var yStepSize: Double
317 | var xTotal: Int
318 | var yTotal: Int
319 | var yLabel: String
320 | var xLabel: String
321 |
322 | }
323 |
324 | struct GridInfo {
325 | var xScale: Double
326 | var yScale: Double
327 | var origin: CGPoint
328 | var geo: GeometryProxy
329 | }
330 |
331 | struct Grid: View {
332 |
333 | @Binding var cardIndex: Int
334 | var config: GridConfig
335 |
336 | @ViewBuilder
337 | var content: (GridInfo) -> Content
338 |
339 | var body: some View {
340 | GeometryReader { geo in
341 | ZStack{
342 | Color("neutral100")
343 |
344 | // secondary lines
345 | Path { path in
346 | let xStepLength = (geo.size.width) / CGFloat(config.xTotal)
347 | let yStepLength = (geo.size.height) / CGFloat(config.yTotal)
348 |
349 | // x guide lines
350 | for i in 0...config.xTotal {
351 | path.move(to: CGPoint(x: CGFloat(0.0), y: geo.size.height - (yStepLength * CGFloat(i))))
352 | path.addLine(to: CGPoint(x: geo.size.width, y: geo.size.height - (yStepLength * CGFloat(i))))
353 | }
354 |
355 | // y guide lines
356 | for i in 0...config.yTotal {
357 | path.move(to: CGPoint(x: xStepLength * CGFloat(i), y: CGFloat(0.0)))
358 | path.addLine(to: CGPoint(x: xStepLength * CGFloat(i), y: geo.size.height))
359 |
360 | }
361 | }
362 | .stroke(Color("neutral400"), lineWidth: 1)
363 |
364 |
365 | //main axises
366 | Path { path in
367 |
368 | // x axis
369 | path.move(to: CGPoint(x: CGFloat(0), y: geo.size.height))
370 | path.addLine(to: CGPoint(x: geo.size.width, y: geo.size.height))
371 |
372 | // y axis
373 | path.move(to: CGPoint.zero)
374 | path.addLine(to: CGPoint(x: 0, y: geo.size.height))
375 |
376 |
377 | }
378 | .stroke(.black, lineWidth: 2)
379 |
380 | //labels
381 |
382 | ForEach(0...config.yTotal, id: \.self) { i in
383 | Text(String(config.yStepSize * CGFloat(i)))
384 | .font(.system(size:12))
385 | .foregroundColor(Color("neutral300"))
386 | .position(x: (geo.size.width) / CGFloat(config.xTotal) * CGFloat(i), y: geo.size.height + 13)
387 | }
388 |
389 | ForEach(0...config.xTotal, id: \.self) { i in
390 | Text(String(config.xStepSize * CGFloat(i)))
391 | .font(.system(size:12))
392 | .foregroundColor(Color("neutral300"))
393 | .position(x: -16, y: geo.size.height - ((geo.size.height) / CGFloat(config.yTotal) * CGFloat(i)))
394 | }
395 |
396 | //axis labels
397 |
398 | Text(config.xLabel)
399 | .font(.system(size:14))
400 | .foregroundColor(Color("neutral500"))
401 | .position(x: (geo.size.width) / 2, y: geo.size.height + 29)
402 |
403 | Text(config.yLabel)
404 | .font(.system(size:14))
405 | .foregroundColor(Color("neutral500"))
406 | .rotationEffect(Angle(degrees: 270))
407 | .position(x: -33, y: geo.size.height / 2 )
408 |
409 | //have the content
410 | content(GridInfo(xScale: Double(geo.size.width) / Double(config.xTotal), yScale: Double(geo.size.height) / Double(config.yTotal), origin: CGPoint(x: 0.0, y: geo.size.height),geo:geo))
411 |
412 |
413 | }
414 | .onAppear {
415 | }
416 | }
417 | .padding()
418 | .padding(.bottom)
419 | }
420 | }
421 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Views/2D/LearnView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LearnView.swift
3 | // GradientDescend
4 | //
5 | // Created by Ryan D on 4/5/22.
6 | //
7 |
8 | import Foundation
9 | import SwiftUI
10 |
11 | struct Learn2DGDView: View {
12 | @State var slide = 0
13 | @Binding var geo: GeometryProxy?
14 | @Binding var zoomedOut: Bool
15 | @Binding var selectedView: MainView
16 | var body: some View {
17 | MoreComplex2DGDSlide(slide: $slide, zoomedOut: self.$zoomedOut, selectedView: $selectedView)
18 | }
19 | }
20 |
21 |
22 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Views/2D/Slides2D.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Slides2D.swift
3 | //
4 | //
5 | // Created by Ryan D on 4/6/22.
6 | //
7 |
8 | import Foundation
9 | import SwiftUI
10 |
11 | struct Bubble: Identifiable, Hashable {
12 | var id = UUID()
13 | var offsetX: Double
14 | var offsetY: Double
15 | var color1: Color
16 | var color2: Color
17 | var x: Double
18 | var y: Double
19 | }
20 |
21 |
22 | struct Basic2DGradientDescentSlide: View {
23 | let textFont = Font
24 | .system(size: 20)
25 | .monospaced()
26 |
27 | let boldedTextFont = Font
28 | .system(size: 20)
29 | .bold()
30 | .monospaced()
31 |
32 | let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
33 |
34 | @State var time = 0
35 | @State var cardIndex = 0
36 | @State var hideOverlayLine = false
37 |
38 | @State var geo: GeometryProxy? = nil
39 |
40 | @State var xValue = 0.0
41 |
42 | @State var counter = 0
43 |
44 | @Binding var zoomedOut: Bool
45 | @Binding var selectedView: MainView
46 | var body: some View {
47 |
48 | ZStack {
49 | VStack {
50 | HStack {
51 | // explanation
52 | VStack(alignment: .leading) {
53 | Text("How does Gradient Descent Work?")
54 | .font(.largeTitle)
55 | .bold()
56 | .padding()
57 | .padding(.bottom)
58 | ScrollView {
59 | ScrollViewReader { value in
60 | VStack(alignment: .leading) {
61 |
62 |
63 | // at the same time card index 1, show the graph
64 | Group {
65 | Text("The graph on the right plots a neural network's parameters on the ").font(textFont) + Text("x axis").font(boldedTextFont) + Text(", and the corresponding error given by those parameters on the ").font(textFont) + Text("y axis.").font(boldedTextFont)
66 | }.padding()
67 | .opacity(cardIndex > 0 ? 1 : 0)
68 | .animation(.spring(), value: self.cardIndex)
69 | .id(0)
70 |
71 | //when its card index 2, wait 2 seconds and have the animation of the steps
72 | Group {
73 | Text("The model is initialized at an ").font(textFont) + Text("arbitrary point").font(boldedTextFont) + Text(" and the Gradient Descent algorithm adjusts the model to minimize the error as much as possible.").font(textFont)
74 | }.padding()
75 | .opacity(cardIndex > 1 ? 1 : 0)
76 | .animation(.spring(), value: self.cardIndex)
77 | .id(1)
78 |
79 | Group {
80 | Text("You try first! Adjust the slider below to get the model to have the lowest error.").font(textFont)
81 | }.padding()
82 | .opacity(cardIndex > 2 ? 1 : 0)
83 | .animation(.spring(), value: self.cardIndex)
84 | .id(2)
85 |
86 | Slider(value: $xValue, in: 0...10, onEditingChanged: { editing in
87 | if round(xValue) == 5.0 {
88 | self.counter += 1
89 | self.cardIndex += 1
90 | }
91 | }).padding()
92 | .opacity(cardIndex > 2 ? 1 : 0)
93 | .id(3)
94 | .disabled(cardIndex != 3)
95 |
96 | Group {
97 | Text("You got it! ").font(textFont).foregroundColor(.green)
98 | }.padding()
99 | .opacity(cardIndex > 3 ? 1 : 0)
100 | .animation(.spring(), value: self.cardIndex)
101 | .id(4)
102 |
103 |
104 | Group {
105 | Text("Gradient descent calculates the ").font(textFont) + Text("derivative").font(boldedTextFont) + Text(" (or slope) of the current point and adjusts the model towards the ").font(textFont) + Text("steepest downward slope").font(boldedTextFont) + Text(" until it reaches the smallest error possible.").font(textFont)
106 | }.padding()
107 | .opacity(cardIndex > 4 ? 1 : 0)
108 | .animation(.spring(), value: self.cardIndex)
109 | .id(5)
110 |
111 | Group {
112 | Text("This is ").font(textFont) + Text("gradient descent").font(boldedTextFont) + Text(" in action! ").font(textFont)
113 | }.padding()
114 | .opacity(cardIndex > 5 ? 1 : 0)
115 | .animation(.spring(), value: self.cardIndex)
116 | .id(6)
117 |
118 |
119 |
120 | Group {
121 | Text("Now you should have a basic understanding of how gradient descent works! Let's move on to the next section.").font(textFont)
122 | }.padding()
123 | .opacity(cardIndex > 6 ? 1 : 0)
124 | .animation(.spring(), value: self.cardIndex)
125 | .id(7)
126 |
127 | Spacer()
128 |
129 | }
130 |
131 | .onChange(of: self.cardIndex) { _ in
132 | withAnimation(.easeInOut) {
133 | value.scrollTo(self.cardIndex - 1, anchor: .top)
134 | }
135 | }
136 |
137 | }
138 | }
139 |
140 | if cardIndex <= 6 && cardIndex != 3 {
141 | Button(action: {
142 | self.cardIndex += 1
143 | }, label: {
144 | Text("Next")
145 | .font(boldedTextFont)
146 | .padding()
147 | .padding(.bottom)
148 | .padding(.bottom)
149 | .irregularGradient(colors: [Color("bg5"),Color("bg3"),Color("bg4")], backgroundColor: Color("bg4"))
150 | .scaleEffect(self.time % 2 == 0 ? 1 : 1.1)
151 | .animation(.easeInOut(duration: 1), value: self.time)
152 | })
153 | }
154 | if self.cardIndex >= 7 {
155 | Button(action: {
156 | withAnimation(.spring()) {
157 | self.zoomedOut = true
158 | }
159 | DispatchQueue.main.asyncAfter(deadline: .now() + 1){
160 | withAnimation(.spring()) {
161 | self.selectedView = .learn2DGDView
162 | }
163 | }
164 | DispatchQueue.main.asyncAfter(deadline: .now() + 2){
165 | withAnimation(.spring()) {
166 | self.zoomedOut = false
167 | }
168 | }
169 | }, label: {
170 | Text("Next")
171 | .font(.system(size: 23)
172 | .bold()
173 | .monospaced())
174 | .padding()
175 | .padding(.bottom)
176 | .padding(.bottom)
177 | .irregularGradient(colors: [Color("bg5"),Color("bg3"),Color("bg4")])
178 | .scaleEffect(self.time % 2 == 0 ? 1 : 1.1)
179 | .animation(.easeInOut(duration: 1), value: self.time)
180 | })
181 | }
182 | }
183 |
184 | // interactive stuff !!!
185 | HStack {
186 | SimpleInteractiveGraph2DView(cardIndex: $cardIndex, xValue: $xValue)
187 | .opacity(self.hideOverlayLine ? 1 : 0)
188 | .animation(.default, value: self.hideOverlayLine)
189 | }
190 | }
191 | .padding(.top)
192 | .padding()
193 | }
194 | ConfettiCannon(counter: self.$counter, num: 100, radius: 450)
195 | }
196 | .onAppear {
197 | }
198 | .onReceive(timer) { date in
199 | self.time += 1
200 | if self.time > 0 {
201 | self.hideOverlayLine = true
202 | }
203 | }
204 |
205 | .overlay(
206 | GeometryReader { geo in
207 | Text("")
208 | .onAppear {
209 | self.geo = geo
210 | }
211 | }
212 |
213 | )
214 |
215 | }
216 | }
217 |
218 |
219 | struct MoreComplex2DGDSlide: View {
220 | @Binding var slide: Int
221 | let textFont = Font
222 | .system(size: 20)
223 | .monospaced()
224 | let boldedTextFont = Font
225 | .system(size: 20)
226 | .bold()
227 | .monospaced()
228 |
229 | let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
230 |
231 | @State var time = 0
232 | @State var cardIndex = 0
233 | @State var hideOverlayLine = false
234 |
235 | @State var geo: GeometryProxy? = nil
236 |
237 | @State var xValue = 0.0
238 |
239 | @State var counter = 0
240 |
241 | @State var learningRate = 0.01
242 |
243 | @State var learningRateIndx = 1.0
244 |
245 | @Binding var zoomedOut: Bool
246 | @Binding var selectedView: MainView
247 | var body: some View {
248 |
249 | ZStack {
250 | VStack {
251 | HStack {
252 | // explanation
253 | VStack(alignment: .leading) {
254 | Text("Learning Rates and Local Minima")
255 | .font(.largeTitle)
256 | .bold()
257 | .padding()
258 | .padding(.bottom)
259 | ScrollView {
260 | ScrollViewReader { value in
261 | VStack(alignment: .leading) {
262 |
263 | // at the same time card index 1, show the graph
264 | Group {
265 | Text("""
266 | Now let's look at a more complex example of gradient descent.
267 |
268 | Here, we have a model with two
269 | """).font(textFont) + Text(" local minima").font(boldedTextFont) + Text(" (canyons), lets run gradient descent and observe what happens. ").font(textFont) + Text(" (click next)").font(boldedTextFont)
270 | }.padding()
271 | .opacity(cardIndex > 0 ? 1 : 0)
272 | .animation(.spring(), value: self.cardIndex)
273 | .id(0)
274 |
275 | //when its card index 2, wait 2 seconds and have the animation of the steps
276 | Group {
277 | Text("Did you notice how gradient descent didn't fully reduce the error?").font(textFont) + Text("""
278 |
279 |
280 |
281 | Gradient descent is like a ball rolling down a hill, it wont be able to climb out of a local minima.
282 | """).font(textFont)
283 | }.padding()
284 | .opacity(cardIndex > 1 ? 1 : 0)
285 | .animation(.spring(), value: self.cardIndex)
286 | .id(1)
287 |
288 | Group {
289 | Text("""
290 | To resolve this problem, we can adjust the learning rate, a number that controls the magnitude of each step.
291 |
292 |
293 | Adjust the slider below and experiment with different learning rates.
294 | """).font(textFont)
295 |
296 | }.padding()
297 | .opacity(cardIndex > 2 ? 1 : 0)
298 | .animation(.spring(), value: self.cardIndex)
299 | .id(2)
300 |
301 | Group {
302 |
303 | Slider(value: $learningRateIndx, in: 1.0...4.0, onEditingChanged: { editing in
304 | // todo [0.001,0.005,0.01,0.10,]
305 | // default is 0.005
306 | if Int(round(learningRateIndx)) == 1 {
307 | self.learningRate = 0.001
308 | self.learningRateIndx = 1
309 | } else if Int(round(learningRateIndx)) == 2 {
310 | self.learningRate = 0.005
311 | self.learningRateIndx = 2
312 | } else if Int(round(learningRateIndx)) == 3 {
313 | self.learningRate = 0.01
314 | self.learningRateIndx = 3
315 | } else {
316 | self.learningRate = 0.1
317 | self.learningRateIndx = 4
318 | }
319 | }).padding()
320 |
321 | HStack{
322 | Spacer()
323 | Text(String(format: "Learning rate: %.3f", learningRate))
324 | .font(.system(size: 14).monospaced())
325 | .padding()
326 | Spacer()
327 | }
328 | }
329 | .opacity(cardIndex > 2 ? 1 : 0)
330 | .id(2)
331 |
332 | Group {
333 | Text("""
334 |
335 | As you can see, higher learning rate can let the model explore more of the feature space but risks skipping over the minimum possible.
336 |
337 |
338 | """).font(textFont) + Text("You finished this module on learning rates!").font(boldedTextFont) + Text(" Let's move onto some examples that are even more fun!").font(textFont)
339 |
340 | }.padding()
341 | .opacity(cardIndex > 3 ? 1 : 0)
342 | .animation(.spring(), value: self.cardIndex)
343 | .id(3)
344 |
345 |
346 | Spacer()
347 |
348 |
349 | }
350 |
351 | .onChange(of: self.cardIndex) { _ in
352 | withAnimation(.easeInOut) {
353 | value.scrollTo(self.cardIndex - 1, anchor: .top)
354 | }
355 | }
356 | }
357 | }
358 | if cardIndex <= 3 {
359 | Button(action: {
360 | if self.cardIndex < 4 {
361 | self.cardIndex += 1
362 | } else {
363 | self.slide += 1
364 | }
365 | }, label: {
366 | Text("Next")
367 | .font(boldedTextFont)
368 | .padding()
369 | .padding(.bottom)
370 | .padding(.bottom)
371 | .irregularGradient(colors: [Color("bg5"),Color("bg3"),Color("bg4")], backgroundColor: Color("bg4"))
372 | .scaleEffect(self.time % 2 == 0 ? 1 : 1.1)
373 | .animation(.easeInOut(duration: 1), value: self.time)
374 | })
375 | }
376 | if self.cardIndex > 3 {
377 | Button(action: {
378 | withAnimation(.spring()) {
379 | self.zoomedOut = true
380 | }
381 | DispatchQueue.main.asyncAfter(deadline: .now() + 1){
382 | withAnimation(.spring()) {
383 | self.selectedView = .learn3DGDView
384 | }
385 | }
386 | DispatchQueue.main.asyncAfter(deadline: .now() + 2){
387 | withAnimation(.spring()) {
388 | self.zoomedOut = false
389 | }
390 | }
391 | }, label: {
392 | Text("Go to Multidimensional Module")
393 | .font(.system(size: 23)
394 | .bold()
395 | .monospaced())
396 | .padding()
397 | .padding(.bottom)
398 | .padding(.bottom)
399 | .irregularGradient(colors: [Color("bg5"),Color("bg3"),Color("bg4")])
400 | .scaleEffect(self.time % 2 == 0 ? 1 : 1.1)
401 | .animation(.easeInOut(duration: 1), value: self.time)
402 | })
403 | }
404 | }
405 |
406 | // intearctive stuff !!!
407 | HStack {
408 | ComplexInteractiveGraph2DView(cardIndex: $cardIndex, xValue: $xValue, learningRateIndx: $learningRateIndx)
409 | .opacity(self.hideOverlayLine ? 1 : 0)
410 | .animation(.default, value: self.hideOverlayLine)
411 | }
412 | }
413 | .padding(.top)
414 | .padding()
415 | }
416 | }
417 | .onReceive(timer) { date in
418 | self.time += 1
419 | if self.time > 0 {
420 | self.hideOverlayLine = true
421 | }
422 | }
423 |
424 | .overlay(
425 | GeometryReader { geo in
426 | Text("")
427 | .onAppear {
428 | self.geo = geo
429 | }
430 | }
431 | )
432 | }
433 | }
434 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Views/3D/3DGraph.swift:
--------------------------------------------------------------------------------
1 | //
2 | // 3DGraph.swift
3 | //
4 | //
5 | // Created by Ryan D on 4/17/22.
6 | //
7 |
8 | import Foundation
9 | import SwiftUI
10 | import SceneKit
11 |
12 |
13 | struct Simple3DView: View, Equatable {
14 | static func == (lhs: Simple3DView, rhs: Simple3DView) -> Bool {
15 | return true
16 | }
17 |
18 | @Binding var dropBall: Bool
19 |
20 |
21 | @State var scene = Simple3DScene(make: true)
22 | var body: some View {
23 | SceneView(scene: scene, options: [.autoenablesDefaultLighting, .allowsCameraControl])
24 | .ignoresSafeArea()
25 | .onChange(of: self.dropBall) { i in
26 | self.scene.ballDropped()
27 | }
28 | }
29 |
30 |
31 | }
32 |
33 | class Simple3DScene: SCNScene {
34 | var modelPosition: SCNVector3? = nil
35 |
36 | convenience init(make:Bool){
37 | self.init()
38 |
39 | background.contents = UIColor.black
40 |
41 |
42 | // MARK: Grid
43 | let stepSize = 1.0
44 | let xAxisTotal = 10
45 | let yAxisTotal = 10
46 | let zAxisTotal = 10
47 | let scale: CGFloat = 3.0
48 |
49 | let grid = createGrid(stepSize: stepSize, xAxisTotal: xAxisTotal, yAxisTotal: yAxisTotal, zAxisTotal: zAxisTotal, xLabel: "Param 1", yLabel: "Error Rate", zLabel: "Param 2", scale: scale)
50 | grid.position = SCNVector3(x: 0, y: 0, z: 0)
51 | rootNode.addChildNode(grid)
52 |
53 | //create the graph
54 |
55 | let modelGraph = SCNScene(named: "simple3dgd.scn")!.rootNode
56 | modelGraph.scale = SCNVector3(x: 3.0 * Float(scale), y: 3.0 * Float(scale), z: 3.0 * Float(scale))
57 | self.modelPosition = SCNVector3(x: Float(xAxisTotal / 2) * Float(stepSize * scale), y: 0.0, z: Float(zAxisTotal / 2) * Float(stepSize * scale))
58 | modelGraph.position = self.modelPosition!
59 | modelGraph.geometry?.materials[0].isDoubleSided = true
60 |
61 | let modelGraphPhysicsBody = SCNPhysicsBody(type: .static, shape: SCNPhysicsShape(
62 | node: modelGraph,
63 | options: [
64 | .collisionMargin: 0.0,
65 | .type: SCNPhysicsShape.ShapeType.concavePolyhedron
66 | ]
67 | ))
68 | modelGraph.physicsBody = modelGraphPhysicsBody
69 |
70 | rootNode.addChildNode(modelGraph)
71 |
72 | }
73 |
74 | // TODO: have a function that starts the gradient descent animation...
75 |
76 |
77 | func createGrid(stepSize: Double, xAxisTotal: Int, yAxisTotal: Int, zAxisTotal: Int, xLabel: String, yLabel: String, zLabel: String, scale: CGFloat) -> SCNNode {
78 | let mainNode = SCNNode()
79 |
80 |
81 | //AXISES
82 | //x
83 | let xAxis = SCNNode(geometry: SCNCylinder(radius: 0.1, height: CGFloat(xAxisTotal) * scale))
84 | xAxis.position = SCNVector3(x: 0.0, y: -1 * Float(CGFloat(yAxisTotal) * scale / 2), z: Float(CGFloat(zAxisTotal) * scale / 2))
85 | xAxis.rotation = SCNVector4(1, 0, 0, 1.5708)
86 | mainNode.addChildNode(xAxis)
87 | //y
88 | let yAxis = SCNNode(geometry: SCNCylinder(radius: 0.1, height: CGFloat(yAxisTotal) * scale))
89 | mainNode.addChildNode(yAxis)
90 | //z
91 | let zAxis = SCNNode(geometry: SCNCylinder(radius: 0.1, height: CGFloat(zAxisTotal) * scale))
92 | zAxis.position = SCNVector3(x: Float(CGFloat(xAxisTotal) * scale) / 2, y: -1 * Float(CGFloat(yAxisTotal) * scale / 2), z: 0.0)
93 | zAxis.rotation = SCNVector4(0, 0, 1, 1.5708)
94 | mainNode.addChildNode(zAxis)
95 |
96 | // labels
97 | let textMaterial = SCNMaterial()
98 | textMaterial.diffuse.contents = UIColor.white
99 | // y
100 | let yLabelText = SCNText(string: "Error Rate", extrusionDepth: 2)
101 | yLabelText.materials = [textMaterial]
102 |
103 | let yLabel = SCNNode(geometry: yLabelText)
104 | yLabel.scale = SCNVector3(x: 0.1, y: 0.1, z: 0.1)
105 | yLabel.position = SCNVector3(x: -0.3, y: 0, z: 0)
106 | yLabel.rotation = SCNVector4(0, 0, 1, -1.5708)
107 | rootNode.addChildNode(yLabel)
108 |
109 |
110 | //XY
111 | for x in 0...xAxisTotal {
112 | let node = SCNNode(geometry: SCNCylinder(radius: 0.02, height: CGFloat(yAxisTotal) * scale))
113 | node.position = SCNVector3(x: Float(scale * CGFloat(x)), y: 0.0, z: 0.0)
114 | mainNode.addChildNode(node)
115 | }
116 | for y in 0...yAxisTotal {
117 | let node = SCNNode(geometry: SCNCylinder(radius: 0.02, height: CGFloat(xAxisTotal) * scale))
118 | node.rotation = SCNVector4(0, 0, 1, 1.5708)
119 | node.position = SCNVector3(x: Float(CGFloat(xAxisTotal) * scale) / 2, y: Float(CGFloat(y) * scale) - Float(CGFloat(yAxisTotal) * scale) / 2, z: 0.0)
120 | mainNode.addChildNode(node)
121 | }
122 |
123 |
124 | //YZ
125 | for z in 0...zAxisTotal {
126 | let node = SCNNode(geometry: SCNCylinder(radius: 0.02, height: CGFloat(yAxisTotal) * scale))
127 | node.position = SCNVector3(x: 0.0, y: 0.0, z: Float(scale * CGFloat(z)))
128 | mainNode.addChildNode(node)
129 | }
130 | for y in 0...yAxisTotal {
131 | let node = SCNNode(geometry: SCNCylinder(radius: 0.02, height: CGFloat(zAxisTotal) * scale))
132 | node.rotation = SCNVector4(1, 0, 0, 1.5708)
133 | node.position = SCNVector3(x: 0.0, y: Float(CGFloat(y) * scale) - Float(CGFloat(yAxisTotal) * scale) / 2, z: Float(CGFloat(zAxisTotal) * scale) / 2)
134 | mainNode.addChildNode(node)
135 | }
136 |
137 |
138 | //XZ
139 | for y in 0...yAxisTotal {
140 | let node = SCNNode(geometry: SCNCylinder(radius: 0.02, height: CGFloat(zAxisTotal) * scale))
141 | node.rotation = SCNVector4(1, 0, 0, 1.5708)
142 | node.position = SCNVector3(x: Float(CGFloat(y) * scale), y: -1 * Float(CGFloat(yAxisTotal) * scale) / 2, z: Float(CGFloat(zAxisTotal) * scale) / 2)
143 | mainNode.addChildNode(node)
144 | }
145 | for y in 0...yAxisTotal {
146 | let node = SCNNode(geometry: SCNCylinder(radius: 0.02, height: CGFloat(xAxisTotal) * scale))
147 | node.rotation = SCNVector4(0, 0, 1, 1.5708)
148 | node.position = SCNVector3(x: Float(CGFloat(xAxisTotal) * scale) / 2, y: -1 * Float(CGFloat(yAxisTotal) * scale) / 2, z: Float(CGFloat(y) * scale))
149 | mainNode.addChildNode(node)
150 | }
151 |
152 | return mainNode
153 | }
154 |
155 | func ballDropped() {
156 | guard let modelPosition = modelPosition else {
157 | return
158 | }
159 | let ball = SCNNode(geometry: SCNSphere(radius: 0.5))
160 | var ballPosition = modelPosition
161 | ballPosition.y = ballPosition.y + 10
162 | ball.position = ballPosition
163 |
164 | let wwdcMaterialGurl = SCNMaterial()
165 | wwdcMaterialGurl.isDoubleSided = false
166 | wwdcMaterialGurl.diffuse.contents = UIImage(named: "wwdc")
167 |
168 | ball.geometry?.materials = [wwdcMaterialGurl]
169 | let ballPhysicsBody = SCNPhysicsBody(type: .dynamic, shape: SCNPhysicsShape(geometry: SCNSphere(radius: 0.5)))
170 | ballPhysicsBody.isAffectedByGravity = true
171 |
172 | ball.physicsBody = ballPhysicsBody
173 | self.rootNode.addChildNode(ball)
174 |
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Views/3D/MultiDimensionalGDView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MultiDimensionalGDView.swift
3 | // GradientDescend
4 | //
5 | // Created by Ryan D on 4/5/22.
6 | //
7 |
8 | import Foundation
9 | import SwiftUI
10 |
11 | struct Learn3DGDViewGDView: View {
12 | @State var slide = 0
13 | @Binding var geo: GeometryProxy?
14 | @Binding var zoomedOut: Bool
15 | @Binding var selectedView: MainView
16 | var body: some View {
17 | Simple3DGDSlide(slide: $slide)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Views/3D/Slides3D.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Slides3D.swift
3 | //
4 | //
5 | // Created by Ryan D on 4/17/22.
6 | //
7 |
8 | import Foundation
9 | import SwiftUI
10 |
11 |
12 | struct Simple3DGDSlide: View {
13 | @Binding var slide: Int
14 | let textFont = Font
15 | .system(size: 20)
16 | .monospaced()
17 | let boldedTextFont = Font
18 | .system(size: 20)
19 | .bold()
20 | .monospaced()
21 |
22 | let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
23 |
24 | @State var time = 0
25 | @State var cardIndex = 0
26 | @State var hideOverlayLine = false
27 |
28 | @State var geo: GeometryProxy? = nil
29 |
30 | @State var xValue = 0.0
31 |
32 | @State var counter = 0
33 |
34 | @State var dropBall = false
35 |
36 |
37 | var body: some View {
38 |
39 | ZStack {
40 | Color.black
41 | VStack {
42 | HStack {
43 | // explanation
44 | VStack(alignment: .leading) {
45 | Text("3D Gradient Descent")
46 | .font(.largeTitle)
47 | .bold()
48 | .padding()
49 | .padding(.bottom)
50 | .foregroundColor(.white)
51 | ScrollView {
52 | ScrollViewReader { value in
53 | VStack(alignment: .leading) {
54 |
55 |
56 | // at the same time card index 1, show the graph
57 | Group {
58 | Text("Now let's take a look at an example of a neural network with ").font(textFont).foregroundColor(.white) + Text("two parameters").font(boldedTextFont).foregroundColor(.white) + Text(". This takes us to the third dimension!").font(textFont).foregroundColor(.white)
59 | }.padding()
60 | .opacity(cardIndex > 0 ? 1 : 0)
61 | .animation(.spring(), value: self.cardIndex)
62 | .id(0)
63 |
64 | Group {
65 | Text("To simulate gradient descent, let's drop a ball and let it minimize the error of this model. Pan around to get a good view of the model, then click drop.").font(textFont).foregroundColor(.white)
66 |
67 | Button(action: {
68 | self.dropBall.toggle()
69 | DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
70 | self.cardIndex += 1
71 | }
72 | }, label: {
73 | Text("Drop Ball")
74 | .foregroundColor(.white)
75 | .padding()
76 | .background(
77 | RoundedRectangle(cornerRadius: 24)
78 | .fill(Color("bg4"))
79 | )
80 | })
81 | }.padding()
82 | .opacity(cardIndex > 1 ? 1 : 0)
83 | .animation(.spring(), value: self.cardIndex)
84 | .id(1)
85 |
86 |
87 |
88 | Group {
89 | Text("Congratulations!").font(boldedTextFont).foregroundColor(Color("bg4")) + Text("""
90 | You've completed this playground app! Thank you for your time and I hope you have a great WWDC2022!
91 |
92 | If you'd like, you can go back to the previous modules with the button on the bottom right.
93 | """).font(textFont).foregroundColor(.white)
94 | }.padding()
95 | .opacity(cardIndex > 2 ? 1 : 0)
96 | .animation(.spring(), value: self.cardIndex)
97 | .id(2)
98 |
99 | Spacer()
100 |
101 |
102 | }
103 |
104 | .onChange(of: self.cardIndex) { _ in
105 | if self.cardIndex == 3 {
106 | self.counter += 1
107 | }
108 | withAnimation(.easeInOut) {
109 | value.scrollTo(self.cardIndex - 1, anchor: .top)
110 | }
111 | }
112 |
113 | }
114 | }
115 |
116 | if self.cardIndex < 3 && self.cardIndex != 2 {
117 | Button(action: {
118 | if self.cardIndex < 7 {
119 | self.cardIndex += 1
120 | } else {
121 | self.slide += 1
122 | }
123 | }, label: {
124 | Text("Next")
125 | .font(boldedTextFont)
126 | .padding()
127 | .padding(.bottom)
128 | .padding(.bottom)
129 | .irregularGradient(colors: [Color("bg5"),Color("bg3"),Color("bg4")], backgroundColor: Color("bg4"))
130 | .scaleEffect(self.time % 2 == 0 ? 1 : 1.1)
131 | .animation(.easeInOut(duration: 1), value: self.time)
132 | })
133 | }
134 | }
135 |
136 | // interactive stuff !!!
137 | HStack {
138 |
139 | Simple3DView(dropBall: $dropBall)
140 | .id("grid")
141 | }
142 | }
143 | .padding(.top)
144 | .padding()
145 | }
146 | ConfettiCannon(counter: self.$counter, num: 100, radius: 450)
147 | }
148 | .onAppear {
149 | }
150 | .onReceive(timer) { date in
151 | self.time += 1
152 | if self.time > 0 {
153 | self.hideOverlayLine = true
154 | }
155 | }
156 |
157 | .overlay(
158 | GeometryReader { geo in
159 | Text("")
160 | .onAppear {
161 | self.geo = geo
162 | }
163 | }
164 |
165 | )
166 |
167 | }
168 | }
169 |
170 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Views/ContentView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ContentView.swift
3 | // GradientDescend
4 | //
5 | // Created by Ryan D on 4/5/22.
6 | //
7 |
8 | import SwiftUI
9 | import AVFoundation
10 |
11 |
12 | /*
13 | Hey There! 👋
14 |
15 | I'm Ryan, welcome to my swift playground app, GradientDescend!
16 | In this playground app we will descend into how Gradient Descent works!
17 |
18 | GradientDescend is best experienced full screen in landscape.
19 | The build process might take a moment so hold tight enjoy!
20 |
21 | */
22 |
23 |
24 |
25 | struct ContentView: View {
26 | @State var finishedOnboarding = false
27 | @State var player: AVAudioPlayer? = nil
28 | var body: some View {
29 | ZStack{
30 | if self.finishedOnboarding{
31 | TabView()
32 | } else {
33 | OnboardingView(finishedOnboarding: self.$finishedOnboarding)
34 | }
35 | }.onAppear(perform: {
36 | //play intro
37 | let url = Bundle.main.url(forResource: "intro", withExtension: "m4a")
38 | player = try! AVAudioPlayer(contentsOf: url!)
39 | player!.play()
40 | })
41 | .preferredColorScheme(.light)
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Views/MyApp.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MyApp.swift
3 | // GradientDescend
4 | //
5 | // Created by Ryan D on 4/5/22.
6 | //
7 |
8 | import SwiftUI
9 |
10 | @main
11 | struct MyApp: App {
12 | var body: some Scene {
13 | WindowGroup {
14 | ContentView()
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Views/Other/Basic2DView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | // GradientDescend
4 | //
5 | // Created by Ryan D on 4/5/22.
6 | //
7 |
8 | import Foundation
9 | import SwiftUI
10 | import AVFoundation
11 |
12 | struct Basic2DView: View {
13 | @Binding var geo: GeometryProxy?
14 | @Binding var zoomedOut: Bool
15 | @Binding var selectedView: MainView
16 |
17 | var body: some View {
18 | Basic2DGradientDescentSlide(zoomedOut: $zoomedOut, selectedView: $selectedView)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Views/Other/InteractiveWindow.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Ryan D on 4/6/22.
6 | //
7 |
8 | import Foundation
9 | import SwiftUI
10 |
11 | struct InteractiveWindow: View {
12 |
13 | @Binding var slide: Int
14 | let content: [AnyView]
15 |
16 | init(slide: Binding, @ViewBuilder content: @escaping () -> TupleView) {
17 | self.content = content().getViews
18 | self._slide = slide
19 | }
20 |
21 |
22 | var body: some View {
23 | ZStack {
24 | //background
25 | Color.white
26 |
27 | content[self.slide]
28 |
29 | }
30 | // .overlay(ControlsCard(slide:$slide, maxSlides: content.count - 1),alignment: .bottomLeading)
31 | }
32 | }
33 |
34 |
35 |
36 | struct ControlsCard: View {
37 | @Binding var slide: Int
38 | var maxSlides: Int
39 | var body: some View {
40 | HStack {
41 | Button(action: {
42 | withAnimation(.spring()) {
43 | if slide - 1 >= 0 {
44 | slide -= 1
45 | }
46 | }
47 | }, label: {
48 | Image(systemName: "chevron.backward.circle")
49 | .resizable()
50 | .frame(width: 40, height: 40)
51 | .foregroundColor(Color("bg4"))
52 | // .softOuterShadow()
53 | .padding()
54 | })
55 |
56 | Button(action: {
57 | withAnimation(.spring()) {
58 | if slide + 1 <= maxSlides {
59 | slide += 1
60 | }
61 | }
62 | }, label: {
63 | Image(systemName: "chevron.forward.circle")
64 | .resizable()
65 | .frame(width: 40, height: 40)
66 | .foregroundColor(Color("bg4"))
67 | // .softOuterShadow()
68 | .padding()
69 | })
70 | }.padding()
71 | }
72 | }
73 |
74 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Views/Other/OnboardingView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | // GradientDescend
4 | //
5 | // Created by Ryan D on 4/5/22.
6 | //
7 |
8 | import Foundation
9 | import SwiftUI
10 | //import IrregularGradient
11 |
12 | struct OnboardingView: View {
13 |
14 | @Binding var finishedOnboarding: Bool
15 |
16 | @State var geo: GeometryProxy?
17 |
18 | @State var bubbles: [Bubble] = []
19 |
20 | @State var expandOverlay = false
21 | @State var reshrinkOverlay = false
22 | @State var hideContent = false
23 |
24 | @State var animateOnScreen = false
25 |
26 | @State var animateGridX = false
27 | @State var animateGridY = false
28 | var body: some View {
29 | ZStack{
30 | //white background
31 | VStack{
32 | Spacer()
33 | HStack {
34 | Spacer()
35 | Color.white
36 | Spacer()
37 | }
38 | Spacer()
39 | }
40 | //bubbles
41 | Group {
42 | if self.bubbles.count > 0 {
43 | ForEach(self.bubbles, id:\.self) {bub in
44 | Circle()
45 | .fill(
46 | LinearGradient(colors: [bub.color1, bub.color2], startPoint: .top, endPoint: .bottomTrailing)
47 | )
48 | .frame(width: bub.x, height: bub.y)
49 | .offset(x:bub.offsetX, y:bub.offsetY)
50 | .transition(.scale)
51 | }
52 | }
53 | }
54 |
55 | Path { path in
56 | guard let geo = geo else { return }
57 | let stepLength = (geo.size.width) / 12
58 | let yTotal = Int(round(geo.size.width / stepLength))
59 |
60 |
61 | // y guide lines
62 | for i in 0...yTotal {
63 | path.move(to: CGPoint(x: stepLength * CGFloat(i), y: CGFloat(0.0)))
64 | path.addLine(to: CGPoint(x: stepLength * CGFloat(i), y: geo.size.height))
65 |
66 | }
67 | }
68 | .trim(from: 0, to: self.animateGridY ? 1 : 0)
69 | .stroke(Color("neutral400"), lineWidth: 1)
70 | .animation(.easeInOut(duration: 4), value: self.animateGridY)
71 |
72 | Path { path in
73 | guard let geo = geo else { return }
74 | let stepLength = (geo.size.width) / 12
75 | let xTotal = Int(round(geo.size.height / stepLength))
76 |
77 | // x guide lines
78 | for i in 0...xTotal {
79 | path.move(to: CGPoint(x: CGFloat(0.0), y: geo.size.height - (stepLength * CGFloat(i))))
80 | path.addLine(to: CGPoint(x: geo.size.width, y: geo.size.height - (stepLength * CGFloat(i))))
81 | }
82 | }
83 | .trim(from: 0, to: self.animateGridX ? 1 : 0)
84 | .stroke(Color("neutral400"), lineWidth: 1)
85 | .animation(.easeOut(duration: 4), value: self.animateGridX)
86 |
87 |
88 | //content
89 | VStack {
90 | VStack {
91 | Spacer()
92 | VStack {
93 |
94 |
95 | Text("Gradient")
96 | .font(.system(size: 80).bold().monospaced())
97 |
98 | .offset(x: self.animateOnScreen ? 0 : 300)
99 | .opacity(self.animateOnScreen ? 1 : 0)
100 | .animation(.easeInOut(duration: 1), value: self.animateOnScreen)
101 | .irregularGradient(colors: [Color("gd1-1"), Color("gd1-2"), Color("gd1-3"), Color("gd1-4"), Color("gd1-5")], backgroundColor: .white, speed: 2)
102 |
103 | Text("Descent")
104 | .font(.system(size: 80).bold().monospaced())
105 |
106 | .bold()
107 | .offset(x: self.animateOnScreen ? 0 : 300)
108 | .opacity(self.animateOnScreen ? 1 : 0)
109 | .animation(.easeInOut(duration: 1), value: self.animateOnScreen)
110 | .irregularGradient(colors: [Color("gd1-1"), Color("gd1-2"), Color("gd1-3"), Color("gd1-4"), Color("gd1-5")], backgroundColor: .white, speed: 2)
111 | }
112 |
113 |
114 | Text("(An algorithm for training and optimizing neural networks)")
115 | .font(.system(size: 18).monospaced())
116 |
117 | .offset(x: self.animateOnScreen ? 0 : -300)
118 | .opacity(self.animateOnScreen ? 1 : 0)
119 | .animation(.easeInOut(duration: 1), value: self.animateOnScreen)
120 | .padding(.top, 3)
121 |
122 | Spacer()
123 | }
124 | Spacer()
125 |
126 | Text("Tap Anywhere to Start")
127 | .font(.system(size: 25).monospaced())
128 | .offset(y: self.animateOnScreen ? 0 : -30)
129 | .animation(.spring(), value: self.animateOnScreen)
130 | .padding()
131 | .padding()
132 | Spacer()
133 | }
134 | if self.hideContent {
135 | Color.white
136 | }
137 | }
138 | .background(GeometryReader { geo in
139 | Text("")
140 | .onAppear(perform: {
141 | self.geo = geo
142 | })
143 | })
144 | .overlay(
145 | ZStack {
146 | Circle()
147 | .fill(
148 | LinearGradient(colors: [Color("bg3"),Color("bg4")], startPoint: .top, endPoint: .bottomTrailing)
149 | )
150 | .opacity(self.reshrinkOverlay ? 0 : 1)
151 | .frame(width: self.expandOverlay ? 2000 : 0, height: self.expandOverlay ? 2000 : 0)
152 | .transition(.scale)
153 | .animation(.spring(response: 0.8, dampingFraction: 0.6, blendDuration: 1), value: self.expandOverlay)
154 | .offset(x: 0, y: self.reshrinkOverlay ? (geo?.size.height ?? 100) / -2 + 100 : 0)
155 | .edgesIgnoringSafeArea(.all)
156 |
157 | Circle()
158 | .frame(width: 1500, height: 1500)
159 | .foregroundColor(.white)
160 | .opacity(self.reshrinkOverlay ? 1 : 0)
161 | .animation(.easeInOut(duration: 1), value: self.reshrinkOverlay)
162 | .edgesIgnoringSafeArea(.all)
163 | }
164 |
165 | )
166 | .onTapGesture {
167 | if self.animateGridY {
168 |
169 | withAnimation(.spring()) {
170 | for _ in 0...200 {
171 | DispatchQueue.main.asyncAfter(deadline: .now() + Double.random(in: 0.2...2)) {
172 | addBubble()
173 | }
174 | }
175 | }
176 | // expand a big gradient and then cool off into white and next slide!
177 |
178 |
179 | DispatchQueue.main.asyncAfter(deadline: .now() + 2.5) {
180 | self.expandOverlay = true
181 | }
182 |
183 | DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
184 | self.bubbles = []
185 | self.hideContent = true
186 | self.reshrinkOverlay = true
187 | self.expandOverlay = false
188 | }
189 |
190 | DispatchQueue.main.asyncAfter(deadline: .now() + 4) {
191 | self.finishedOnboarding = true
192 |
193 | }
194 | }
195 |
196 | }
197 | .onAppear {
198 | withAnimation() {
199 | self.animateOnScreen = true
200 | }
201 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: {
202 | withAnimation() {
203 | self.animateGridX = true
204 | }
205 | })
206 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.6, execute: {
207 | withAnimation() {
208 | self.animateGridY = true
209 | }
210 | })
211 |
212 | }
213 | }
214 | func randomRainbowColor() -> Color {
215 | let colors: [Color] = [Color("bg2"),Color("bg3"),Color("bg4")]
216 | return colors.randomElement()!
217 | }
218 | func randomOffset() -> Double {
219 | return Double.random(in: -800...800)
220 | }
221 | func addBubble() {
222 | withAnimation(.spring(
223 | response: 0.7,
224 | dampingFraction: 0.5,
225 | blendDuration: 0.9
226 | )) {
227 | self.bubbles.append(Bubble(offsetX: randomOffset(), offsetY: randomOffset(), color1: randomRainbowColor(), color2: randomRainbowColor(),x:80*Double.random(in: 2...5),y:80*Double.random(in: 2...5)))
228 | }
229 | }
230 | }
231 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Views/Other/SupportingViews.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SupportingViews.swift
3 | //
4 | //
5 | // Created by Ryan D on 4/5/22.
6 | //
7 |
8 | import Foundation
9 | import SwiftUI
10 |
11 | // code from https://stackoverflow.com/questions/64238485/how-to-loop-over-viewbuilder-content-subviews-in-swiftui
12 | // used to help with sorting through each view inside Interactive Window
13 | extension TupleView {
14 | var getViews: [AnyView] {
15 | makeArray(from: value)
16 | }
17 |
18 | private struct GenericView {
19 | let body: Any
20 |
21 | var anyView: AnyView? {
22 | AnyView(_fromValue: body)
23 | }
24 | }
25 |
26 | private func makeArray(from tuple: Tuple) -> [AnyView] {
27 | func convert(child: Mirror.Child) -> AnyView? {
28 | withUnsafeBytes(of: child.value) { ptr -> AnyView? in
29 | let binded = ptr.bindMemory(to: GenericView.self)
30 | return binded.first?.anyView
31 | }
32 | }
33 |
34 | let tupleMirror = Mirror(reflecting: tuple)
35 | return tupleMirror.children.compactMap(convert)
36 | }
37 | }
38 |
39 | // code from https://www.fivestars.blog/articles/conditional-modifiers/
40 | // I used this snippet because this simple extension to view makes it a lot more
41 | // convenient to deal with modifying views conditionally and prevents duplicate code.
42 | extension View {
43 | @ViewBuilder
44 | func `if`(_ condition: Bool, if ifTransform: (Self) -> TrueContent, else elseTransform: (Self) -> FalseContent) -> some View {
45 | if condition {
46 | ifTransform(self)
47 | } else {
48 | elseTransform(self)
49 | }
50 | }
51 | @ViewBuilder
52 | func `if`(_ condition: Bool, transform: (Self) -> Transform) -> some View {
53 | if condition {
54 | transform(self)
55 | } else {
56 | self
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/GradientDescend.swiftpm/Views/TabView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TabView.swift
3 | // GradientDescend
4 | //
5 | // Created by Ryan D on 4/5/22.
6 | //
7 |
8 | import Foundation
9 | import SwiftUI
10 | import CoreMotion
11 |
12 |
13 | enum MainView {
14 | case learn2DGDView
15 | case learn3DGDView
16 | case basic2dView
17 | }
18 |
19 |
20 | struct TabView: View {
21 | @State var zoomedOut = false
22 | @State var offset = CGSize.zero
23 | @State var selectedView: MainView = .basic2dView
24 | @State var geo: GeometryProxy?
25 |
26 | var body: some View {
27 |
28 | // stack the zoomed out background behind at all times
29 | ZStack (alignment: .center){
30 |
31 | //background
32 |
33 |
34 | Learn3DGDViewGDView(geo:$geo,zoomedOut: self.$zoomedOut, selectedView: $selectedView)
35 | .displayContainerHelper(moduleName: "3. 3D Gradient Descent", selectedView: self.$selectedView, zoomedOut: self.$zoomedOut, viewType: .learn3DGDView, geo: $geo)
36 | .gyroscope3DEffect(zoomedOut: $zoomedOut)
37 |
38 | Basic2DView(geo:$geo,zoomedOut: self.$zoomedOut, selectedView: $selectedView)
39 | .displayContainerHelper(moduleName: "1. How Does Gradient Descent Work", selectedView: self.$selectedView, zoomedOut: self.$zoomedOut, viewType: .basic2dView, geo: $geo)
40 | .gyroscope3DEffect(zoomedOut: $zoomedOut)
41 |
42 | Learn2DGDView(geo:$geo,zoomedOut: self.$zoomedOut, selectedView: $selectedView)
43 | .displayContainerHelper(moduleName: "2. Learning Rates & Local Minima", selectedView: self.$selectedView, zoomedOut: self.$zoomedOut, viewType: .learn2DGDView, geo: $geo)
44 | .gyroscope3DEffect(zoomedOut: $zoomedOut)
45 |
46 | ZStack(alignment: .bottomTrailing) {
47 | Button(action: {
48 | withAnimation(.spring()) {
49 | self.zoomedOut.toggle()
50 | }
51 | }, label: {
52 |
53 | Image(systemName: "square.3.stack.3d")
54 | .resizable()
55 | .frame(width: 20, height: 20)
56 | .irregularGradient(colors: [Color("bg1"),Color("bg2"),Color("bg5"),Color("bg3"),Color("bg4")], backgroundColor: Color("bg4"))
57 | .padding()
58 | .background(Circle().fill(LinearGradient(colors: [Color("neutral200"),Color("neutral400")], startPoint: .topLeading, endPoint: .bottomTrailing)))
59 |
60 | })
61 | }.zIndex(2).offset(x: (geo?.size.width ?? 1000) / 2 - 70, y: (geo?.size.height ?? 1000) / 2 - 70)
62 |
63 |
64 |
65 |
66 | }.ignoresSafeArea(edges: .top)
67 | .background(
68 | Rectangle()
69 | .irregularGradient(colors: [Color("bg1"),Color("bg2"),Color("bg5"),Color("bg3"),Color("bg4")], backgroundColor: Color("bg1"), speed: 12.0)
70 | .scaledToFill()
71 | .ignoresSafeArea()
72 | .zIndex(-1)
73 | .scaleEffect(3)
74 | )
75 |
76 | .background(GeometryReader { geo in
77 | Text("")
78 | .onAppear(perform: {
79 | self.geo = geo
80 | })
81 | })
82 |
83 | }
84 | }
85 |
86 |
87 |
88 | /// helps display container correctly display and animate when zoomed out
89 | struct DisplayContainerHelper: ViewModifier{
90 | var moduleName: String
91 | @Binding var selectedView: MainView
92 | @Binding var zoomedOut: Bool
93 | var viewType: MainView
94 | @Binding var geo: GeometryProxy?
95 |
96 | func body(content: Content) -> some View {
97 | VStack (alignment:.center) {
98 | ZStack{
99 | Color.white
100 | content
101 | }
102 | .disabled(self.zoomedOut)
103 | .frame(width: self.zoomedOut ? self.viewSize().width : nil, height: self.zoomedOut ? self.viewSize().height : nil)
104 | .cornerRadius(zoomedOut ? 18 : 0)
105 | .padding(self.zoomedOut ? 11 : 0)
106 | .background(zoomedOut ? Rectangle().foregroundColor(Color("deviceFrame")).opacity(1).shadow(radius: 5) : Rectangle().foregroundColor(.white).opacity(0).shadow(radius: 5))
107 | .cornerRadius(zoomedOut ? 24 : 0)
108 |
109 | if self.zoomedOut {
110 | Text(moduleName)
111 | .font(.system(size: 26).monospaced().bold())
112 | .foregroundColor(.white)
113 | }
114 | }
115 | .zIndex(self.selectedView == self.viewType ? 1 : 0)
116 |
117 | .offset(x: (self.zoomedOut && self.selectedView != viewType && (viewType == (self.selectedView == .learn2DGDView ? .learn3DGDView : .learn2DGDView))) ? -430 : 0, y: self.zoomedOut && self.selectedView != viewType ? -65 : 0 )
118 | .offset(x: (self.zoomedOut && self.selectedView != viewType && (viewType == (self.selectedView == .basic2dView ? .learn3DGDView : .basic2dView))) ? 430 : 0, y: self.zoomedOut && self.selectedView != viewType ? -65 : 0 )
119 |
120 | .onTapGesture {
121 | if self.zoomedOut {
122 | DispatchQueue.main.asyncAfter(deadline: .now()){
123 | withAnimation(.spring()) {
124 | self.selectedView = self.viewType
125 | }
126 | }
127 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.8){
128 | withAnimation(.spring()) {
129 | self.zoomedOut = false
130 | }
131 | }
132 | }
133 | }
134 | }
135 |
136 | func viewSize() -> CGSize {
137 | let isMain = false
138 | return CGSize(width: isMain ? 640 : 480, height: isMain ? 436 : 327)
139 | }
140 | }
141 |
142 | class MotionManager: ObservableObject {
143 | var motionManager: CMMotionManager
144 | @Published var x = 0.4
145 | @Published var y = 0.2
146 | @Published var z = 0.1
147 |
148 | init() {
149 | self.motionManager = CMMotionManager()
150 | self.motionManager.magnetometerUpdateInterval = 1/30
151 | self.motionManager.startDeviceMotionUpdates(to: .main) {data, error in
152 | if let error = error {
153 | print(error)
154 | }
155 | guard let data = data else { return }
156 | self.objectWillChange.send()
157 | self.x = data.rotationRate.x
158 | self.y = data.rotationRate.y
159 | self.z = data.rotationRate.z
160 |
161 | }
162 | }
163 | }
164 |
165 | struct Gyroscope3DEffect: ViewModifier {
166 | @ObservedObject var motionManager = MotionManager()
167 | @Binding var zoomedOut: Bool
168 | let magnitude = 2.0
169 | func body(content: Content) -> some View {
170 |
171 | content
172 | .rotation3DEffect(Angle(degrees: self.zoomedOut ? motionManager.y * magnitude : 0), axis: (x: 1, y: 0, z: 0))
173 | .rotation3DEffect(Angle(degrees: self.zoomedOut ? motionManager.x * magnitude : 0), axis: (x: 0, y: 1, z: 0))
174 | .rotation3DEffect(Angle(degrees: self.zoomedOut ? motionManager.z * magnitude : 0), axis: (x: 0, y: 0, z: 1))
175 | }
176 | }
177 |
178 | extension View {
179 | func displayContainerHelper(moduleName: String, selectedView: Binding, zoomedOut: Binding,viewType: MainView, geo:Binding) -> some View{
180 | return modifier(DisplayContainerHelper(moduleName: moduleName, selectedView: selectedView, zoomedOut: zoomedOut, viewType: viewType, geo:geo))
181 | }
182 |
183 | func gyroscope3DEffect(zoomedOut: Binding) -> some View {
184 | return modifier(Gyroscope3DEffect(zoomedOut: zoomedOut))
185 | }
186 | }
187 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Ryan
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # GradientDescend
2 |
3 | Welcome to GradientDescend! This is my submission for the WWDC 2022 Swift Student Challenge.
4 | If you'd like, you can check out GradientDescend's [**Trailer Video**](https://www.youtube.com/watch?v=TINWpa961VE)
5 |
6 | 
7 |
8 |
9 | **GradientDescend** is an interactive playground that explores the concept of **gradient descent**. Descend into multiple interactive demos that give you different perspectives to understand the concept of gradient descend. GradientDescent was built in **SwiftUI** and encorperated aspects of **CoreMotion**, **SceneKit**, and other open source packages for cool effects and interactive demos.
10 |
11 | ## Open Source Packages
12 | I'd like to attribute and thank these open source projects for creating such amazing content that helped craft my submission. Thank you so much Joāo, Simon, and all the contributors!
13 | ### [**IrregularGradients**](https://github.com/joogps/IrregularGradient)
14 | IrregularGradients is a package built by **João Gabriel** and provides beautiful animating gradients. I used it in GradientDescent to create the beautiful background and fun buttons.
15 |
16 | ### [**ConfettiSwiftUI**](https://github.com/simibac/ConfettiSwiftUI)
17 | ConfettiSwiftui is a package built by **Simon Bachmann** that contains a variety of customizable SwiftUI confetti. The confetti displayed in GradientDescend is from the ConfettiSwiftUI package and adds a fun element to GradientDescend.
18 |
19 | ## Thanks 🙃
20 | I'd like to thank some of my **friends** for their inspiration and support during my dubdub journey.
21 |
22 | Thank you [aheze](https://github.com/aheze) and his [Club Day App](https://github.com/AHSCodingClub/ClubDay2022) for inspiration on the bubbles!
23 |
24 | Thank you Madhav for going through DubDub with me and keeping me sane during ssc weekend.
25 |
26 | Thank you Christina, Allyson, Claire, and Monica for supporting my DubDub grind and being there for me as awesome friends!
27 |
28 | ## Images
29 |
30 | 
31 | 
32 | 
33 |
--------------------------------------------------------------------------------
/Screen Shot 2022-04-24 at 10.00.09 PM (2).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ryendu/GradientDescend/c751956c75a9ee1a4b8338a4323f82296ab18c44/Screen Shot 2022-04-24 at 10.00.09 PM (2).png
--------------------------------------------------------------------------------
/gradientDescent3dSimple.blend:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ryendu/GradientDescend/c751956c75a9ee1a4b8338a4323f82296ab18c44/gradientDescent3dSimple.blend
--------------------------------------------------------------------------------
/gradientDescent3dSimple.blend1:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ryendu/GradientDescend/c751956c75a9ee1a4b8338a4323f82296ab18c44/gradientDescent3dSimple.blend1
--------------------------------------------------------------------------------
/gradientDescent3dSimple.mtl:
--------------------------------------------------------------------------------
1 | # Blender MTL File: 'gradientDescent3dSimple.blend'
2 | # Material Count: 1
3 |
4 | newmtl None
5 | Ns 500
6 | Ka 0.8 0.8 0.8
7 | Kd 0.8 0.8 0.8
8 | Ks 0.8 0.8 0.8
9 | d 1
10 | illum 2
11 |
--------------------------------------------------------------------------------