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

3 | 4 | 5 | 6 | Twitter: @joogps 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 | ![Screen Shot 2022-04-28 at 9 53 24 PM](https://user-images.githubusercontent.com/61951438/165878538-a95b2355-c63c-4b83-b0b9-7fbd98a1bfb9.png) 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 | ![Screen Shot 2022-04-28 at 9 46 31 PM](https://user-images.githubusercontent.com/61951438/165878500-b90474fc-2482-441a-913e-b5a1b3a4bcd1.png) 31 | ![Screen Shot 2022-04-28 at 9 54 17 PM](https://user-images.githubusercontent.com/61951438/165878604-69e2f90a-1afb-431b-94c1-d2f582861f67.png) 32 | ![Screen Shot 2022-04-28 at 9 54 36 PM](https://user-images.githubusercontent.com/61951438/165878626-a9dae619-df51-4aa6-b730-a87548399ce4.png) 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 | --------------------------------------------------------------------------------