├── LICENSE ├── README.md ├── ShiftUI.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings │ └── xcuserdata │ │ └── ryanlintott.xcuserdatad │ │ └── WorkspaceSettings.xcsettings └── xcuserdata │ └── ryanlintott.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── ShiftUI ├── AnimationStyle.swift ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── app-icon-1024@1x~ios-marketing.png │ │ ├── app-icon-20@1x~ipad.png │ │ ├── app-icon-20@2x~ipad.png │ │ ├── app-icon-20@2x~iphone.png │ │ ├── app-icon-20@3x~iphone.png │ │ ├── app-icon-29@1x~ipad.png │ │ ├── app-icon-29@2x~ipad.png │ │ ├── app-icon-29@2x~iphone.png │ │ ├── app-icon-29@3x~iphone.png │ │ ├── app-icon-40@1x~ipad.png │ │ ├── app-icon-40@2x~ipad.png │ │ ├── app-icon-40@2x~iphone.png │ │ ├── app-icon-40@3x~iphone.png │ │ ├── app-icon-60@2x~iphone.png │ │ ├── app-icon-60@3x~iphone.png │ │ ├── app-icon-76@1x~ipad.png │ │ ├── app-icon-76@2x~ipad.png │ │ └── app-icon-83.5@2x~ipad.png │ └── Contents.json ├── Extension │ ├── Color+Extension.swift │ ├── Gradient+Extension.swift │ ├── NavigationBarColor.swift │ └── UniqueById.swift ├── Info.plist ├── Model │ ├── BoardPosition.swift │ ├── Direction.swift │ ├── Level.swift │ ├── Position.swift │ ├── ShiftBoard.swift │ └── ShiftSquare.swift ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json ├── ShiftUIApp.swift ├── View │ ├── AppIconView.swift │ ├── BigTitleView.swift │ ├── BoardView.swift │ ├── ContentView.swift │ ├── GameView.swift │ ├── NeumorphicButton.swift │ ├── NeumorphicShadow.swift │ ├── ResultView.swift │ ├── ShiftableView.swift │ ├── SuccessView.swift │ ├── TileButton.swift │ ├── TileView.swift │ └── WelcomeView.swift └── ViewManager.swift └── privacy-policy.md /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Ryan Lintott and Md Abir Hasan Zoha 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 | # ShiftUI 2 | A SwiftUI sliding puzzle with a neumorphic design 3 | -------------------------------------------------------------------------------- /ShiftUI.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 896B1FB525E1127C00D8A818 /* SuccessView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 896B1FB425E1127C00D8A818 /* SuccessView.swift */; }; 11 | 896B1FB825E12D8700D8A818 /* NeumorphicShadow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 896B1FB725E12D8700D8A818 /* NeumorphicShadow.swift */; }; 12 | 896B1FBB25E1348400D8A818 /* ResultView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 896B1FBA25E1348400D8A818 /* ResultView.swift */; }; 13 | 89DCCB4625E0EEA500861A66 /* TileButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89DCCB4525E0EEA500861A66 /* TileButton.swift */; }; 14 | 89DCCB4B25E0FDC400861A66 /* Color+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89DCCB4A25E0FDC400861A66 /* Color+Extension.swift */; }; 15 | 89DCCB4F25E0FDD100861A66 /* Gradient+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89DCCB4E25E0FDD100861A66 /* Gradient+Extension.swift */; }; 16 | 89EE975225E167D40011E694 /* WelcomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89EE975125E167D40011E694 /* WelcomeView.swift */; }; 17 | 89EE975525E1744F0011E694 /* NavigationBarColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89EE975425E1744F0011E694 /* NavigationBarColor.swift */; }; 18 | CF00009125E1719E00B103C4 /* ShiftableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF00009025E1719E00B103C4 /* ShiftableView.swift */; }; 19 | CF0EE75625E07EFD002B3601 /* BoardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF434A2525E04B6A0065B8B1 /* BoardView.swift */; }; 20 | CF434A0625E025BD0065B8B1 /* ShiftUIApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF434A0525E025BD0065B8B1 /* ShiftUIApp.swift */; }; 21 | CF434A0825E025BD0065B8B1 /* GameView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF434A0725E025BD0065B8B1 /* GameView.swift */; }; 22 | CF434A0A25E025BE0065B8B1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CF434A0925E025BE0065B8B1 /* Assets.xcassets */; }; 23 | CF434A0D25E025BE0065B8B1 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CF434A0C25E025BE0065B8B1 /* Preview Assets.xcassets */; }; 24 | CF434A1725E02A4F0065B8B1 /* ShiftBoard.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF434A1625E02A4F0065B8B1 /* ShiftBoard.swift */; }; 25 | CF434A1B25E047BC0065B8B1 /* ShiftSquare.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF434A1A25E047BC0065B8B1 /* ShiftSquare.swift */; }; 26 | CF434A2925E054B60065B8B1 /* BoardPosition.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF434A2825E054B60065B8B1 /* BoardPosition.swift */; }; 27 | CF90BCF225E1A53700AF22FC /* UniqueById.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF90BCF125E1A53700AF22FC /* UniqueById.swift */; }; 28 | CF90BCFB25E2ACA800AF22FC /* Level.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF90BCFA25E2ACA800AF22FC /* Level.swift */; }; 29 | CF90BD0225E2C24800AF22FC /* BigTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF90BD0125E2C24800AF22FC /* BigTitleView.swift */; }; 30 | CF90BD0525E2C89500AF22FC /* NeumorphicButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF90BD0425E2C89500AF22FC /* NeumorphicButton.swift */; }; 31 | CF90BD0825E2DA4C00AF22FC /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF90BD0725E2DA4C00AF22FC /* ContentView.swift */; }; 32 | CF90BD0D25E2E24000AF22FC /* ViewManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF90BD0C25E2E24000AF22FC /* ViewManager.swift */; }; 33 | CF90BD1025E2FB2B00AF22FC /* TileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF90BD0F25E2FB2B00AF22FC /* TileView.swift */; }; 34 | CF90BD1325E3009E00AF22FC /* AnimationStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF90BD1225E3009E00AF22FC /* AnimationStyle.swift */; }; 35 | CF90BD1725E312C700AF22FC /* AppIconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF90BD1625E312C700AF22FC /* AppIconView.swift */; }; 36 | CFEDD56225E079AE00553F25 /* Position.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFEDD56125E079AE00553F25 /* Position.swift */; }; 37 | CFEDD56525E079CB00553F25 /* Direction.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFEDD56425E079CB00553F25 /* Direction.swift */; }; 38 | /* End PBXBuildFile section */ 39 | 40 | /* Begin PBXFileReference section */ 41 | 896B1FB425E1127C00D8A818 /* SuccessView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuccessView.swift; sourceTree = ""; }; 42 | 896B1FB725E12D8700D8A818 /* NeumorphicShadow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NeumorphicShadow.swift; sourceTree = ""; }; 43 | 896B1FBA25E1348400D8A818 /* ResultView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultView.swift; sourceTree = ""; }; 44 | 89DCCB4525E0EEA500861A66 /* TileButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TileButton.swift; sourceTree = ""; }; 45 | 89DCCB4A25E0FDC400861A66 /* Color+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+Extension.swift"; sourceTree = ""; }; 46 | 89DCCB4E25E0FDD100861A66 /* Gradient+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Gradient+Extension.swift"; sourceTree = ""; }; 47 | 89EE975125E167D40011E694 /* WelcomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeView.swift; sourceTree = ""; }; 48 | 89EE975425E1744F0011E694 /* NavigationBarColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBarColor.swift; sourceTree = ""; }; 49 | CF00009025E1719E00B103C4 /* ShiftableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShiftableView.swift; sourceTree = ""; }; 50 | CF434A0225E025BD0065B8B1 /* ShiftUI.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ShiftUI.app; sourceTree = BUILT_PRODUCTS_DIR; }; 51 | CF434A0525E025BD0065B8B1 /* ShiftUIApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShiftUIApp.swift; sourceTree = ""; }; 52 | CF434A0725E025BD0065B8B1 /* GameView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameView.swift; sourceTree = ""; }; 53 | CF434A0925E025BE0065B8B1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 54 | CF434A0C25E025BE0065B8B1 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 55 | CF434A0E25E025BE0065B8B1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 56 | CF434A1625E02A4F0065B8B1 /* ShiftBoard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShiftBoard.swift; sourceTree = ""; }; 57 | CF434A1A25E047BC0065B8B1 /* ShiftSquare.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShiftSquare.swift; sourceTree = ""; }; 58 | CF434A2525E04B6A0065B8B1 /* BoardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoardView.swift; sourceTree = ""; }; 59 | CF434A2825E054B60065B8B1 /* BoardPosition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoardPosition.swift; sourceTree = ""; }; 60 | CF90BCF125E1A53700AF22FC /* UniqueById.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UniqueById.swift; sourceTree = ""; }; 61 | CF90BCFA25E2ACA800AF22FC /* Level.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Level.swift; sourceTree = ""; }; 62 | CF90BD0125E2C24800AF22FC /* BigTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BigTitleView.swift; sourceTree = ""; }; 63 | CF90BD0425E2C89500AF22FC /* NeumorphicButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NeumorphicButton.swift; sourceTree = ""; }; 64 | CF90BD0725E2DA4C00AF22FC /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 65 | CF90BD0C25E2E24000AF22FC /* ViewManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewManager.swift; sourceTree = ""; }; 66 | CF90BD0F25E2FB2B00AF22FC /* TileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TileView.swift; sourceTree = ""; }; 67 | CF90BD1225E3009E00AF22FC /* AnimationStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimationStyle.swift; sourceTree = ""; }; 68 | CF90BD1625E312C700AF22FC /* AppIconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIconView.swift; sourceTree = ""; }; 69 | CFEDD56125E079AE00553F25 /* Position.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Position.swift; sourceTree = ""; }; 70 | CFEDD56425E079CB00553F25 /* Direction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Direction.swift; sourceTree = ""; }; 71 | /* End PBXFileReference section */ 72 | 73 | /* Begin PBXFrameworksBuildPhase section */ 74 | CF4349FF25E025BD0065B8B1 /* Frameworks */ = { 75 | isa = PBXFrameworksBuildPhase; 76 | buildActionMask = 2147483647; 77 | files = ( 78 | ); 79 | runOnlyForDeploymentPostprocessing = 0; 80 | }; 81 | /* End PBXFrameworksBuildPhase section */ 82 | 83 | /* Begin PBXGroup section */ 84 | 89DCCB4925E0FDAB00861A66 /* Extension */ = { 85 | isa = PBXGroup; 86 | children = ( 87 | 89DCCB4A25E0FDC400861A66 /* Color+Extension.swift */, 88 | 89DCCB4E25E0FDD100861A66 /* Gradient+Extension.swift */, 89 | 89EE975425E1744F0011E694 /* NavigationBarColor.swift */, 90 | CF90BCF125E1A53700AF22FC /* UniqueById.swift */, 91 | ); 92 | path = Extension; 93 | sourceTree = ""; 94 | }; 95 | CF4349F925E025BD0065B8B1 = { 96 | isa = PBXGroup; 97 | children = ( 98 | CF434A0425E025BD0065B8B1 /* ShiftUI */, 99 | CF434A0325E025BD0065B8B1 /* Products */, 100 | ); 101 | sourceTree = ""; 102 | }; 103 | CF434A0325E025BD0065B8B1 /* Products */ = { 104 | isa = PBXGroup; 105 | children = ( 106 | CF434A0225E025BD0065B8B1 /* ShiftUI.app */, 107 | ); 108 | name = Products; 109 | sourceTree = ""; 110 | }; 111 | CF434A0425E025BD0065B8B1 /* ShiftUI */ = { 112 | isa = PBXGroup; 113 | children = ( 114 | 89DCCB4925E0FDAB00861A66 /* Extension */, 115 | CF434A0525E025BD0065B8B1 /* ShiftUIApp.swift */, 116 | CF90BD0C25E2E24000AF22FC /* ViewManager.swift */, 117 | CF90BD1225E3009E00AF22FC /* AnimationStyle.swift */, 118 | CF434A1525E02A110065B8B1 /* Model */, 119 | CF434A1F25E04B4A0065B8B1 /* View */, 120 | CF434A0925E025BE0065B8B1 /* Assets.xcassets */, 121 | CF434A0E25E025BE0065B8B1 /* Info.plist */, 122 | CF434A0B25E025BE0065B8B1 /* Preview Content */, 123 | ); 124 | path = ShiftUI; 125 | sourceTree = ""; 126 | }; 127 | CF434A0B25E025BE0065B8B1 /* Preview Content */ = { 128 | isa = PBXGroup; 129 | children = ( 130 | CF434A0C25E025BE0065B8B1 /* Preview Assets.xcassets */, 131 | ); 132 | path = "Preview Content"; 133 | sourceTree = ""; 134 | }; 135 | CF434A1525E02A110065B8B1 /* Model */ = { 136 | isa = PBXGroup; 137 | children = ( 138 | CF434A1625E02A4F0065B8B1 /* ShiftBoard.swift */, 139 | CF90BCFA25E2ACA800AF22FC /* Level.swift */, 140 | CF434A2825E054B60065B8B1 /* BoardPosition.swift */, 141 | CF434A1A25E047BC0065B8B1 /* ShiftSquare.swift */, 142 | CFEDD56125E079AE00553F25 /* Position.swift */, 143 | CFEDD56425E079CB00553F25 /* Direction.swift */, 144 | ); 145 | path = Model; 146 | sourceTree = ""; 147 | }; 148 | CF434A1F25E04B4A0065B8B1 /* View */ = { 149 | isa = PBXGroup; 150 | children = ( 151 | CF90BD0725E2DA4C00AF22FC /* ContentView.swift */, 152 | CF90BD1625E312C700AF22FC /* AppIconView.swift */, 153 | 89EE975125E167D40011E694 /* WelcomeView.swift */, 154 | CF434A0725E025BD0065B8B1 /* GameView.swift */, 155 | CF90BD0125E2C24800AF22FC /* BigTitleView.swift */, 156 | 896B1FBA25E1348400D8A818 /* ResultView.swift */, 157 | CF434A2525E04B6A0065B8B1 /* BoardView.swift */, 158 | CF90BD0F25E2FB2B00AF22FC /* TileView.swift */, 159 | 89DCCB4525E0EEA500861A66 /* TileButton.swift */, 160 | CF90BD0425E2C89500AF22FC /* NeumorphicButton.swift */, 161 | CF00009025E1719E00B103C4 /* ShiftableView.swift */, 162 | 896B1FB425E1127C00D8A818 /* SuccessView.swift */, 163 | 896B1FB725E12D8700D8A818 /* NeumorphicShadow.swift */, 164 | ); 165 | path = View; 166 | sourceTree = ""; 167 | }; 168 | /* End PBXGroup section */ 169 | 170 | /* Begin PBXNativeTarget section */ 171 | CF434A0125E025BD0065B8B1 /* ShiftUI */ = { 172 | isa = PBXNativeTarget; 173 | buildConfigurationList = CF434A1125E025BE0065B8B1 /* Build configuration list for PBXNativeTarget "ShiftUI" */; 174 | buildPhases = ( 175 | CF4349FE25E025BD0065B8B1 /* Sources */, 176 | CF4349FF25E025BD0065B8B1 /* Frameworks */, 177 | CF434A0025E025BD0065B8B1 /* Resources */, 178 | ); 179 | buildRules = ( 180 | ); 181 | dependencies = ( 182 | ); 183 | name = ShiftUI; 184 | productName = ShiftUI; 185 | productReference = CF434A0225E025BD0065B8B1 /* ShiftUI.app */; 186 | productType = "com.apple.product-type.application"; 187 | }; 188 | /* End PBXNativeTarget section */ 189 | 190 | /* Begin PBXProject section */ 191 | CF4349FA25E025BD0065B8B1 /* Project object */ = { 192 | isa = PBXProject; 193 | attributes = { 194 | LastSwiftUpdateCheck = 1240; 195 | LastUpgradeCheck = 1240; 196 | TargetAttributes = { 197 | CF434A0125E025BD0065B8B1 = { 198 | CreatedOnToolsVersion = 12.4; 199 | }; 200 | }; 201 | }; 202 | buildConfigurationList = CF4349FD25E025BD0065B8B1 /* Build configuration list for PBXProject "ShiftUI" */; 203 | compatibilityVersion = "Xcode 9.3"; 204 | developmentRegion = en; 205 | hasScannedForEncodings = 0; 206 | knownRegions = ( 207 | en, 208 | Base, 209 | ); 210 | mainGroup = CF4349F925E025BD0065B8B1; 211 | productRefGroup = CF434A0325E025BD0065B8B1 /* Products */; 212 | projectDirPath = ""; 213 | projectRoot = ""; 214 | targets = ( 215 | CF434A0125E025BD0065B8B1 /* ShiftUI */, 216 | ); 217 | }; 218 | /* End PBXProject section */ 219 | 220 | /* Begin PBXResourcesBuildPhase section */ 221 | CF434A0025E025BD0065B8B1 /* Resources */ = { 222 | isa = PBXResourcesBuildPhase; 223 | buildActionMask = 2147483647; 224 | files = ( 225 | CF434A0D25E025BE0065B8B1 /* Preview Assets.xcassets in Resources */, 226 | CF434A0A25E025BE0065B8B1 /* Assets.xcassets in Resources */, 227 | ); 228 | runOnlyForDeploymentPostprocessing = 0; 229 | }; 230 | /* End PBXResourcesBuildPhase section */ 231 | 232 | /* Begin PBXSourcesBuildPhase section */ 233 | CF4349FE25E025BD0065B8B1 /* Sources */ = { 234 | isa = PBXSourcesBuildPhase; 235 | buildActionMask = 2147483647; 236 | files = ( 237 | CFEDD56225E079AE00553F25 /* Position.swift in Sources */, 238 | CF90BCF225E1A53700AF22FC /* UniqueById.swift in Sources */, 239 | 89DCCB4B25E0FDC400861A66 /* Color+Extension.swift in Sources */, 240 | CF434A1725E02A4F0065B8B1 /* ShiftBoard.swift in Sources */, 241 | CF434A1B25E047BC0065B8B1 /* ShiftSquare.swift in Sources */, 242 | 896B1FB525E1127C00D8A818 /* SuccessView.swift in Sources */, 243 | CF00009125E1719E00B103C4 /* ShiftableView.swift in Sources */, 244 | CF90BD1725E312C700AF22FC /* AppIconView.swift in Sources */, 245 | 896B1FBB25E1348400D8A818 /* ResultView.swift in Sources */, 246 | CF90BD0525E2C89500AF22FC /* NeumorphicButton.swift in Sources */, 247 | CFEDD56525E079CB00553F25 /* Direction.swift in Sources */, 248 | CF90BD1025E2FB2B00AF22FC /* TileView.swift in Sources */, 249 | 89EE975525E1744F0011E694 /* NavigationBarColor.swift in Sources */, 250 | CF90BD0D25E2E24000AF22FC /* ViewManager.swift in Sources */, 251 | 896B1FB825E12D8700D8A818 /* NeumorphicShadow.swift in Sources */, 252 | CF90BD0825E2DA4C00AF22FC /* ContentView.swift in Sources */, 253 | 89DCCB4625E0EEA500861A66 /* TileButton.swift in Sources */, 254 | CF434A2925E054B60065B8B1 /* BoardPosition.swift in Sources */, 255 | 89EE975225E167D40011E694 /* WelcomeView.swift in Sources */, 256 | CF0EE75625E07EFD002B3601 /* BoardView.swift in Sources */, 257 | CF90BD1325E3009E00AF22FC /* AnimationStyle.swift in Sources */, 258 | CF434A0825E025BD0065B8B1 /* GameView.swift in Sources */, 259 | CF90BD0225E2C24800AF22FC /* BigTitleView.swift in Sources */, 260 | 89DCCB4F25E0FDD100861A66 /* Gradient+Extension.swift in Sources */, 261 | CF90BCFB25E2ACA800AF22FC /* Level.swift in Sources */, 262 | CF434A0625E025BD0065B8B1 /* ShiftUIApp.swift in Sources */, 263 | ); 264 | runOnlyForDeploymentPostprocessing = 0; 265 | }; 266 | /* End PBXSourcesBuildPhase section */ 267 | 268 | /* Begin XCBuildConfiguration section */ 269 | CF434A0F25E025BE0065B8B1 /* Debug */ = { 270 | isa = XCBuildConfiguration; 271 | buildSettings = { 272 | ALWAYS_SEARCH_USER_PATHS = NO; 273 | CLANG_ANALYZER_NONNULL = YES; 274 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 275 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 276 | CLANG_CXX_LIBRARY = "libc++"; 277 | CLANG_ENABLE_MODULES = YES; 278 | CLANG_ENABLE_OBJC_ARC = YES; 279 | CLANG_ENABLE_OBJC_WEAK = YES; 280 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 281 | CLANG_WARN_BOOL_CONVERSION = YES; 282 | CLANG_WARN_COMMA = YES; 283 | CLANG_WARN_CONSTANT_CONVERSION = YES; 284 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 285 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 286 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 287 | CLANG_WARN_EMPTY_BODY = YES; 288 | CLANG_WARN_ENUM_CONVERSION = YES; 289 | CLANG_WARN_INFINITE_RECURSION = YES; 290 | CLANG_WARN_INT_CONVERSION = YES; 291 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 292 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 293 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 294 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 295 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 296 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 297 | CLANG_WARN_STRICT_PROTOTYPES = YES; 298 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 299 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 300 | CLANG_WARN_UNREACHABLE_CODE = YES; 301 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 302 | COPY_PHASE_STRIP = NO; 303 | DEBUG_INFORMATION_FORMAT = dwarf; 304 | ENABLE_STRICT_OBJC_MSGSEND = YES; 305 | ENABLE_TESTABILITY = YES; 306 | GCC_C_LANGUAGE_STANDARD = gnu11; 307 | GCC_DYNAMIC_NO_PIC = NO; 308 | GCC_NO_COMMON_BLOCKS = YES; 309 | GCC_OPTIMIZATION_LEVEL = 0; 310 | GCC_PREPROCESSOR_DEFINITIONS = ( 311 | "DEBUG=1", 312 | "$(inherited)", 313 | ); 314 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 315 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 316 | GCC_WARN_UNDECLARED_SELECTOR = YES; 317 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 318 | GCC_WARN_UNUSED_FUNCTION = YES; 319 | GCC_WARN_UNUSED_VARIABLE = YES; 320 | IPHONEOS_DEPLOYMENT_TARGET = 14.4; 321 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 322 | MTL_FAST_MATH = YES; 323 | ONLY_ACTIVE_ARCH = YES; 324 | SDKROOT = iphoneos; 325 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 326 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 327 | }; 328 | name = Debug; 329 | }; 330 | CF434A1025E025BE0065B8B1 /* Release */ = { 331 | isa = XCBuildConfiguration; 332 | buildSettings = { 333 | ALWAYS_SEARCH_USER_PATHS = NO; 334 | CLANG_ANALYZER_NONNULL = YES; 335 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 336 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 337 | CLANG_CXX_LIBRARY = "libc++"; 338 | CLANG_ENABLE_MODULES = YES; 339 | CLANG_ENABLE_OBJC_ARC = YES; 340 | CLANG_ENABLE_OBJC_WEAK = YES; 341 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 342 | CLANG_WARN_BOOL_CONVERSION = YES; 343 | CLANG_WARN_COMMA = YES; 344 | CLANG_WARN_CONSTANT_CONVERSION = YES; 345 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 346 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 347 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 348 | CLANG_WARN_EMPTY_BODY = YES; 349 | CLANG_WARN_ENUM_CONVERSION = YES; 350 | CLANG_WARN_INFINITE_RECURSION = YES; 351 | CLANG_WARN_INT_CONVERSION = YES; 352 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 353 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 354 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 355 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 356 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 357 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 358 | CLANG_WARN_STRICT_PROTOTYPES = YES; 359 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 360 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 361 | CLANG_WARN_UNREACHABLE_CODE = YES; 362 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 363 | COPY_PHASE_STRIP = NO; 364 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 365 | ENABLE_NS_ASSERTIONS = NO; 366 | ENABLE_STRICT_OBJC_MSGSEND = YES; 367 | GCC_C_LANGUAGE_STANDARD = gnu11; 368 | GCC_NO_COMMON_BLOCKS = YES; 369 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 370 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 371 | GCC_WARN_UNDECLARED_SELECTOR = YES; 372 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 373 | GCC_WARN_UNUSED_FUNCTION = YES; 374 | GCC_WARN_UNUSED_VARIABLE = YES; 375 | IPHONEOS_DEPLOYMENT_TARGET = 14.4; 376 | MTL_ENABLE_DEBUG_INFO = NO; 377 | MTL_FAST_MATH = YES; 378 | SDKROOT = iphoneos; 379 | SWIFT_COMPILATION_MODE = wholemodule; 380 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 381 | VALIDATE_PRODUCT = YES; 382 | }; 383 | name = Release; 384 | }; 385 | CF434A1225E025BE0065B8B1 /* Debug */ = { 386 | isa = XCBuildConfiguration; 387 | buildSettings = { 388 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 389 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 390 | CODE_SIGN_STYLE = Automatic; 391 | DEVELOPMENT_ASSET_PATHS = "\"ShiftUI/Preview Content\""; 392 | DEVELOPMENT_TEAM = 97423JQ8B9; 393 | ENABLE_PREVIEWS = YES; 394 | INFOPLIST_FILE = ShiftUI/Info.plist; 395 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 396 | LD_RUNPATH_SEARCH_PATHS = ( 397 | "$(inherited)", 398 | "@executable_path/Frameworks", 399 | ); 400 | PRODUCT_BUNDLE_IDENTIFIER = com.abetterwaytodo.ShiftUI; 401 | PRODUCT_NAME = "$(TARGET_NAME)"; 402 | SWIFT_VERSION = 5.0; 403 | TARGETED_DEVICE_FAMILY = "1,2"; 404 | }; 405 | name = Debug; 406 | }; 407 | CF434A1325E025BE0065B8B1 /* Release */ = { 408 | isa = XCBuildConfiguration; 409 | buildSettings = { 410 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 411 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 412 | CODE_SIGN_STYLE = Automatic; 413 | DEVELOPMENT_ASSET_PATHS = "\"ShiftUI/Preview Content\""; 414 | DEVELOPMENT_TEAM = 97423JQ8B9; 415 | ENABLE_PREVIEWS = YES; 416 | INFOPLIST_FILE = ShiftUI/Info.plist; 417 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 418 | LD_RUNPATH_SEARCH_PATHS = ( 419 | "$(inherited)", 420 | "@executable_path/Frameworks", 421 | ); 422 | PRODUCT_BUNDLE_IDENTIFIER = com.abetterwaytodo.ShiftUI; 423 | PRODUCT_NAME = "$(TARGET_NAME)"; 424 | SWIFT_VERSION = 5.0; 425 | TARGETED_DEVICE_FAMILY = "1,2"; 426 | }; 427 | name = Release; 428 | }; 429 | /* End XCBuildConfiguration section */ 430 | 431 | /* Begin XCConfigurationList section */ 432 | CF4349FD25E025BD0065B8B1 /* Build configuration list for PBXProject "ShiftUI" */ = { 433 | isa = XCConfigurationList; 434 | buildConfigurations = ( 435 | CF434A0F25E025BE0065B8B1 /* Debug */, 436 | CF434A1025E025BE0065B8B1 /* Release */, 437 | ); 438 | defaultConfigurationIsVisible = 0; 439 | defaultConfigurationName = Release; 440 | }; 441 | CF434A1125E025BE0065B8B1 /* Build configuration list for PBXNativeTarget "ShiftUI" */ = { 442 | isa = XCConfigurationList; 443 | buildConfigurations = ( 444 | CF434A1225E025BE0065B8B1 /* Debug */, 445 | CF434A1325E025BE0065B8B1 /* Release */, 446 | ); 447 | defaultConfigurationIsVisible = 0; 448 | defaultConfigurationName = Release; 449 | }; 450 | /* End XCConfigurationList section */ 451 | }; 452 | rootObject = CF4349FA25E025BD0065B8B1 /* Project object */; 453 | } 454 | -------------------------------------------------------------------------------- /ShiftUI.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ShiftUI.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ShiftUI.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ShiftUI.xcodeproj/project.xcworkspace/xcuserdata/ryanlintott.xcuserdatad/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildLocationStyle 6 | UseAppPreferences 7 | CustomBuildLocationType 8 | RelativeToDerivedData 9 | DerivedDataLocationStyle 10 | Default 11 | IssueFilterStyle 12 | ShowActiveSchemeOnly 13 | LiveSourceIssuesEnabled 14 | 15 | ShowSharedSchemesAutomaticallyEnabled 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /ShiftUI.xcodeproj/xcuserdata/ryanlintott.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | ShiftUI.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /ShiftUI/AnimationStyle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AnimationStyles.swift 3 | // ShiftUI 4 | // 5 | // Created by Ryan Lintott on 2021-02-21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | enum AnimationStyle { 11 | case neumorphic, instant, textFade 12 | 13 | static let durationMultiplier: Double = 1 14 | 15 | var duration: Double { 16 | switch self { 17 | case .instant: return 0.01 * Self.durationMultiplier 18 | case .textFade: return 1 * Self.durationMultiplier 19 | case .neumorphic: return 0.5 * Self.durationMultiplier 20 | } 21 | } 22 | 23 | var animation: Animation { 24 | switch self { 25 | default: return .easeInOut(duration: self.duration) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /ShiftUI/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /ShiftUI/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "app-icon-20@2x~iphone.png", 5 | "idiom" : "iphone", 6 | "scale" : "2x", 7 | "size" : "20x20" 8 | }, 9 | { 10 | "filename" : "app-icon-20@3x~iphone.png", 11 | "idiom" : "iphone", 12 | "scale" : "3x", 13 | "size" : "20x20" 14 | }, 15 | { 16 | "filename" : "app-icon-29@2x~iphone.png", 17 | "idiom" : "iphone", 18 | "scale" : "2x", 19 | "size" : "29x29" 20 | }, 21 | { 22 | "filename" : "app-icon-29@3x~iphone.png", 23 | "idiom" : "iphone", 24 | "scale" : "3x", 25 | "size" : "29x29" 26 | }, 27 | { 28 | "filename" : "app-icon-40@2x~iphone.png", 29 | "idiom" : "iphone", 30 | "scale" : "2x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "filename" : "app-icon-40@3x~iphone.png", 35 | "idiom" : "iphone", 36 | "scale" : "3x", 37 | "size" : "40x40" 38 | }, 39 | { 40 | "filename" : "app-icon-60@2x~iphone.png", 41 | "idiom" : "iphone", 42 | "scale" : "2x", 43 | "size" : "60x60" 44 | }, 45 | { 46 | "filename" : "app-icon-60@3x~iphone.png", 47 | "idiom" : "iphone", 48 | "scale" : "3x", 49 | "size" : "60x60" 50 | }, 51 | { 52 | "filename" : "app-icon-20@1x~ipad.png", 53 | "idiom" : "ipad", 54 | "scale" : "1x", 55 | "size" : "20x20" 56 | }, 57 | { 58 | "filename" : "app-icon-20@2x~ipad.png", 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "20x20" 62 | }, 63 | { 64 | "filename" : "app-icon-29@1x~ipad.png", 65 | "idiom" : "ipad", 66 | "scale" : "1x", 67 | "size" : "29x29" 68 | }, 69 | { 70 | "filename" : "app-icon-29@2x~ipad.png", 71 | "idiom" : "ipad", 72 | "scale" : "2x", 73 | "size" : "29x29" 74 | }, 75 | { 76 | "filename" : "app-icon-40@1x~ipad.png", 77 | "idiom" : "ipad", 78 | "scale" : "1x", 79 | "size" : "40x40" 80 | }, 81 | { 82 | "filename" : "app-icon-40@2x~ipad.png", 83 | "idiom" : "ipad", 84 | "scale" : "2x", 85 | "size" : "40x40" 86 | }, 87 | { 88 | "filename" : "app-icon-76@1x~ipad.png", 89 | "idiom" : "ipad", 90 | "scale" : "1x", 91 | "size" : "76x76" 92 | }, 93 | { 94 | "filename" : "app-icon-76@2x~ipad.png", 95 | "idiom" : "ipad", 96 | "scale" : "2x", 97 | "size" : "76x76" 98 | }, 99 | { 100 | "filename" : "app-icon-83.5@2x~ipad.png", 101 | "idiom" : "ipad", 102 | "scale" : "2x", 103 | "size" : "83.5x83.5" 104 | }, 105 | { 106 | "filename" : "app-icon-1024@1x~ios-marketing.png", 107 | "idiom" : "ios-marketing", 108 | "scale" : "1x", 109 | "size" : "1024x1024" 110 | } 111 | ], 112 | "info" : { 113 | "author" : "xcode", 114 | "version" : 1 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /ShiftUI/Assets.xcassets/AppIcon.appiconset/app-icon-1024@1x~ios-marketing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryanlintott/ShiftUI/ad21ff631b056dc0d7225edd342c5fe5cd03e405/ShiftUI/Assets.xcassets/AppIcon.appiconset/app-icon-1024@1x~ios-marketing.png -------------------------------------------------------------------------------- /ShiftUI/Assets.xcassets/AppIcon.appiconset/app-icon-20@1x~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryanlintott/ShiftUI/ad21ff631b056dc0d7225edd342c5fe5cd03e405/ShiftUI/Assets.xcassets/AppIcon.appiconset/app-icon-20@1x~ipad.png -------------------------------------------------------------------------------- /ShiftUI/Assets.xcassets/AppIcon.appiconset/app-icon-20@2x~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryanlintott/ShiftUI/ad21ff631b056dc0d7225edd342c5fe5cd03e405/ShiftUI/Assets.xcassets/AppIcon.appiconset/app-icon-20@2x~ipad.png -------------------------------------------------------------------------------- /ShiftUI/Assets.xcassets/AppIcon.appiconset/app-icon-20@2x~iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryanlintott/ShiftUI/ad21ff631b056dc0d7225edd342c5fe5cd03e405/ShiftUI/Assets.xcassets/AppIcon.appiconset/app-icon-20@2x~iphone.png -------------------------------------------------------------------------------- /ShiftUI/Assets.xcassets/AppIcon.appiconset/app-icon-20@3x~iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryanlintott/ShiftUI/ad21ff631b056dc0d7225edd342c5fe5cd03e405/ShiftUI/Assets.xcassets/AppIcon.appiconset/app-icon-20@3x~iphone.png -------------------------------------------------------------------------------- /ShiftUI/Assets.xcassets/AppIcon.appiconset/app-icon-29@1x~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryanlintott/ShiftUI/ad21ff631b056dc0d7225edd342c5fe5cd03e405/ShiftUI/Assets.xcassets/AppIcon.appiconset/app-icon-29@1x~ipad.png -------------------------------------------------------------------------------- /ShiftUI/Assets.xcassets/AppIcon.appiconset/app-icon-29@2x~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryanlintott/ShiftUI/ad21ff631b056dc0d7225edd342c5fe5cd03e405/ShiftUI/Assets.xcassets/AppIcon.appiconset/app-icon-29@2x~ipad.png -------------------------------------------------------------------------------- /ShiftUI/Assets.xcassets/AppIcon.appiconset/app-icon-29@2x~iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryanlintott/ShiftUI/ad21ff631b056dc0d7225edd342c5fe5cd03e405/ShiftUI/Assets.xcassets/AppIcon.appiconset/app-icon-29@2x~iphone.png -------------------------------------------------------------------------------- /ShiftUI/Assets.xcassets/AppIcon.appiconset/app-icon-29@3x~iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryanlintott/ShiftUI/ad21ff631b056dc0d7225edd342c5fe5cd03e405/ShiftUI/Assets.xcassets/AppIcon.appiconset/app-icon-29@3x~iphone.png -------------------------------------------------------------------------------- /ShiftUI/Assets.xcassets/AppIcon.appiconset/app-icon-40@1x~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryanlintott/ShiftUI/ad21ff631b056dc0d7225edd342c5fe5cd03e405/ShiftUI/Assets.xcassets/AppIcon.appiconset/app-icon-40@1x~ipad.png -------------------------------------------------------------------------------- /ShiftUI/Assets.xcassets/AppIcon.appiconset/app-icon-40@2x~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryanlintott/ShiftUI/ad21ff631b056dc0d7225edd342c5fe5cd03e405/ShiftUI/Assets.xcassets/AppIcon.appiconset/app-icon-40@2x~ipad.png -------------------------------------------------------------------------------- /ShiftUI/Assets.xcassets/AppIcon.appiconset/app-icon-40@2x~iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryanlintott/ShiftUI/ad21ff631b056dc0d7225edd342c5fe5cd03e405/ShiftUI/Assets.xcassets/AppIcon.appiconset/app-icon-40@2x~iphone.png -------------------------------------------------------------------------------- /ShiftUI/Assets.xcassets/AppIcon.appiconset/app-icon-40@3x~iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryanlintott/ShiftUI/ad21ff631b056dc0d7225edd342c5fe5cd03e405/ShiftUI/Assets.xcassets/AppIcon.appiconset/app-icon-40@3x~iphone.png -------------------------------------------------------------------------------- /ShiftUI/Assets.xcassets/AppIcon.appiconset/app-icon-60@2x~iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryanlintott/ShiftUI/ad21ff631b056dc0d7225edd342c5fe5cd03e405/ShiftUI/Assets.xcassets/AppIcon.appiconset/app-icon-60@2x~iphone.png -------------------------------------------------------------------------------- /ShiftUI/Assets.xcassets/AppIcon.appiconset/app-icon-60@3x~iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryanlintott/ShiftUI/ad21ff631b056dc0d7225edd342c5fe5cd03e405/ShiftUI/Assets.xcassets/AppIcon.appiconset/app-icon-60@3x~iphone.png -------------------------------------------------------------------------------- /ShiftUI/Assets.xcassets/AppIcon.appiconset/app-icon-76@1x~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryanlintott/ShiftUI/ad21ff631b056dc0d7225edd342c5fe5cd03e405/ShiftUI/Assets.xcassets/AppIcon.appiconset/app-icon-76@1x~ipad.png -------------------------------------------------------------------------------- /ShiftUI/Assets.xcassets/AppIcon.appiconset/app-icon-76@2x~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryanlintott/ShiftUI/ad21ff631b056dc0d7225edd342c5fe5cd03e405/ShiftUI/Assets.xcassets/AppIcon.appiconset/app-icon-76@2x~ipad.png -------------------------------------------------------------------------------- /ShiftUI/Assets.xcassets/AppIcon.appiconset/app-icon-83.5@2x~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ryanlintott/ShiftUI/ad21ff631b056dc0d7225edd342c5fe5cd03e405/ShiftUI/Assets.xcassets/AppIcon.appiconset/app-icon-83.5@2x~ipad.png -------------------------------------------------------------------------------- /ShiftUI/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /ShiftUI/Extension/Color+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Color+Extension.swift 3 | // ShiftUI 4 | // 5 | // Created by Zoha on 2/20/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | extension Color { 11 | static let offWhite = Color(red: 225/255, green: 225/255, blue: 235/255) 12 | static let grayText = Color(red: 125/255, green: 142/255, blue: 166/255) 13 | } 14 | -------------------------------------------------------------------------------- /ShiftUI/Extension/Gradient+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Gradient+Extension.swift 3 | // ShiftUI 4 | // 5 | // Created by Zoha on 2/20/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | 11 | extension LinearGradient { 12 | init(_ colors: Color...) { 13 | self.init(gradient: Gradient(colors: colors), startPoint: .topLeading, endPoint: .bottomTrailing) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /ShiftUI/Extension/NavigationBarColor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NavigationBarColor.swift 3 | // ShiftUI 4 | // 5 | // Created by Zoha on 2/20/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct NavigationBarColor: ViewModifier { 11 | 12 | init(backgroundColor: Color, tintColor: Color) { 13 | let backgroundColor = UIColor(backgroundColor) 14 | let tintColor = UIColor(tintColor) 15 | 16 | let coloredAppearance = UINavigationBarAppearance() 17 | coloredAppearance.configureWithOpaqueBackground() 18 | coloredAppearance.backgroundColor = backgroundColor 19 | coloredAppearance.titleTextAttributes = [.foregroundColor: tintColor] 20 | coloredAppearance.largeTitleTextAttributes = [.foregroundColor: tintColor] 21 | 22 | UINavigationBar.appearance().standardAppearance = coloredAppearance 23 | UINavigationBar.appearance().scrollEdgeAppearance = coloredAppearance 24 | UINavigationBar.appearance().compactAppearance = coloredAppearance 25 | UINavigationBar.appearance().tintColor = tintColor 26 | } 27 | 28 | func body(content: Content) -> some View { 29 | content 30 | } 31 | } 32 | 33 | extension View { 34 | func navigationBarColor(backgroundColor: Color, tintColor: Color) -> some View { 35 | self.modifier(NavigationBarColor(backgroundColor: backgroundColor, tintColor: tintColor)) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /ShiftUI/Extension/UniqueById.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UniqueById.swift 3 | // ShiftUI 4 | // 5 | // Created by Ryan Lintott on 2020-07-23. 6 | // 7 | 8 | public protocol UniqueById: Identifiable, Hashable { } 9 | 10 | extension UniqueById { 11 | public func hash(into hasher: inout Hasher) { 12 | hasher.combine(id) 13 | } 14 | 15 | static func == (lhs: Self, rhs: Self) -> Bool { 16 | lhs.id == rhs.id 17 | } 18 | } 19 | 20 | 21 | -------------------------------------------------------------------------------- /ShiftUI/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | 28 | UIApplicationSupportsIndirectInputEvents 29 | 30 | UILaunchScreen 31 | 32 | UIRequiredDeviceCapabilities 33 | 34 | armv7 35 | 36 | UISupportedInterfaceOrientations 37 | 38 | UIInterfaceOrientationPortrait 39 | 40 | UISupportedInterfaceOrientations~ipad 41 | 42 | UIInterfaceOrientationPortrait 43 | UIInterfaceOrientationPortraitUpsideDown 44 | UIInterfaceOrientationLandscapeLeft 45 | UIInterfaceOrientationLandscapeRight 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /ShiftUI/Model/BoardPosition.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BoardPosition.swift 3 | // ShiftUI 4 | // 5 | // Created by Ryan Lintott on 2021-02-19. 6 | // 7 | 8 | import Foundation 9 | 10 | enum BoardPosition: Comparable, UniqueById { 11 | case occupied(square: ShiftSquare) 12 | case empty(position: Position) 13 | 14 | var position: Position { 15 | switch self { 16 | case let .occupied(square): 17 | return square.position 18 | case let .empty(position): 19 | return position 20 | } 21 | } 22 | 23 | var isEmpty: Bool { 24 | switch self { 25 | case .empty: 26 | return true 27 | case .occupied: 28 | return false 29 | } 30 | } 31 | 32 | var square: ShiftSquare? { 33 | switch self { 34 | case let .occupied(square): 35 | return square 36 | case .empty: 37 | return nil 38 | } 39 | } 40 | 41 | var id: Position { 42 | switch self { 43 | case let .occupied(square): 44 | return square.id 45 | case let .empty(position): 46 | return Position(row: -position.row, column: -position.column) 47 | } 48 | } 49 | 50 | static func < (lhs: BoardPosition, rhs: BoardPosition) -> Bool { 51 | lhs.position < rhs.position 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /ShiftUI/Model/Direction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Direction.swift 3 | // ShiftUI 4 | // 5 | // Created by Ryan Lintott on 2021-02-19. 6 | // 7 | 8 | import Foundation 9 | 10 | enum Direction { 11 | case up, down, left, right, none 12 | } 13 | -------------------------------------------------------------------------------- /ShiftUI/Model/Level.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Level.swift 3 | // ShiftUI 4 | // 5 | // Created by Ryan Lintott on 2021-02-21. 6 | // 7 | 8 | import Foundation 9 | 10 | enum Level: Int, CaseIterable, Identifiable, RawRepresentable { 11 | case easy, medium, hard 12 | 13 | var id: Int { 14 | self.rawValue 15 | } 16 | var rows: Int { 17 | switch self { 18 | case .easy: 19 | return 4 20 | case .medium: 21 | return 5 22 | case .hard: 23 | return 6 24 | } 25 | } 26 | 27 | var columns: Int { 28 | switch self { 29 | case .easy: 30 | return 3 31 | case .medium: 32 | return 4 33 | case .hard: 34 | return 5 35 | } 36 | } 37 | 38 | var label: String { 39 | switch self { 40 | case .easy: 41 | return "Easy" 42 | case .medium: 43 | return "Medium" 44 | case .hard: 45 | return "Hard" 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /ShiftUI/Model/Position.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Position.swift 3 | // ShiftUI 4 | // 5 | // Created by Ryan Lintott on 2021-02-19. 6 | // 7 | 8 | import Foundation 9 | 10 | struct Position: Equatable, Hashable, Comparable { 11 | let row: Int 12 | let column: Int 13 | 14 | static func < (lhs: Position, rhs: Position) -> Bool { 15 | lhs.row < rhs.row || (lhs.row == rhs.row && lhs.column < rhs.column) 16 | } 17 | 18 | func sharesColumn(with position: Position) -> Bool { 19 | column == position.column 20 | } 21 | 22 | func sharesRow(with position: Position) -> Bool { 23 | row == position.row 24 | } 25 | 26 | func isAbove(_ position: Position) -> Bool { 27 | sharesColumn(with: position) && self < position 28 | } 29 | 30 | func isBelow(_ position: Position) -> Bool { 31 | sharesColumn(with: position) && self > position 32 | } 33 | 34 | func isLeft(of position: Position) -> Bool { 35 | sharesRow(with: position) && self < position 36 | } 37 | 38 | func isRight(of position: Position) -> Bool { 39 | sharesRow(with: position) && self > position 40 | } 41 | 42 | func direction(to position: Position) -> Direction { 43 | if position.isAbove(self) { 44 | return .up 45 | } else if position.isBelow(self) { 46 | return .down 47 | } else if position.isLeft(of: self) { 48 | return .left 49 | } else if position.isRight(of: self) { 50 | return .right 51 | } else { 52 | return .none 53 | } 54 | } 55 | 56 | func isBetween(a: Position, b: Position) -> Bool { 57 | if (self.sharesColumn(with: a) && self.sharesColumn(with: b)) || (self.sharesRow(with: a) && self.sharesRow(with: b)) { 58 | return (a < self && self < b) || (b < self && self < a) 59 | } 60 | return false 61 | } 62 | 63 | func shifting(direction: Direction) -> Position { 64 | switch direction { 65 | case .up: 66 | return Position(row: row - 1, column: column) 67 | case .down: 68 | return Position(row: row + 1, column: column) 69 | case .left: 70 | return Position(row: row, column: column - 1) 71 | case .right: 72 | return Position(row: row, column: column + 1) 73 | case .none: 74 | return self 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /ShiftUI/Model/ShiftBoard.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShiftModel.swift 3 | // ShiftUI 4 | // 5 | // Created by Ryan Lintott on 2021-02-19. 6 | // 7 | 8 | import Foundation 9 | 10 | class ShiftBoard: ObservableObject { 11 | private(set) var totalRows: Int = 0 12 | private(set) var totalColumns: Int = 0 13 | 14 | @Published private(set) var squares: Set = [] 15 | @Published private(set) var emptyPosition: Position = Position(row: 0, column: 0) 16 | @Published private(set) var activeSquare: ShiftSquare? = nil 17 | @Published private(set) var dragAmount: Double = 0 { 18 | didSet { 19 | pushedAmount = max(dragAmount, pushedAmount) 20 | } 21 | } 22 | @Published private(set) var pushedAmount: Double = 0 23 | @Published private(set) var moves: Int = 0 24 | var isSolved: Bool { 25 | squares.isSolved 26 | } 27 | func number(of square: ShiftSquare) -> Int { 28 | (square.solvedPosition.row - 1) * totalColumns + square.solvedPosition.column 29 | } 30 | 31 | var dragDirection: Direction { 32 | guard let activeSquare = activeSquare else { 33 | return .none 34 | } 35 | return shiftableDirection(of: activeSquare) 36 | } 37 | var pushedSquares: Set { 38 | guard let activeSquare = activeSquare else { 39 | return [] 40 | } 41 | return pushableSquares(whenShifting: activeSquare) 42 | } 43 | var shiftingSquares: Set { 44 | guard let activeSquare = activeSquare else { 45 | return [] 46 | } 47 | return pushedSquares.union([activeSquare]) 48 | } 49 | 50 | init(level: Level = .easy) { 51 | initBoard(level: level) 52 | } 53 | 54 | func initBoard(level: Level) { 55 | initBoard(rows: level.rows, columns: level.columns) 56 | } 57 | 58 | func initBoard(rows: Int, columns: Int) { 59 | totalRows = rows 60 | totalColumns = columns 61 | emptyPosition = Position(row: rows, column: columns) 62 | squares = [] 63 | for row in 1...totalRows { 64 | for column in 1...totalColumns { 65 | let squarePosition = Position(row: row, column: column) 66 | if emptyPosition != squarePosition { 67 | squares.insert(ShiftSquare(solvedPosition: squarePosition)) 68 | } 69 | } 70 | } 71 | } 72 | 73 | var positions: [BoardPosition] { 74 | (squares.map({ BoardPosition.occupied(square: $0) }) + [.empty(position: emptyPosition)]).sorted() 75 | } 76 | 77 | func shiftableDirection(of square: ShiftSquare) -> Direction { 78 | square.position.direction(to: emptyPosition) 79 | } 80 | 81 | func pushableSquares(whenShifting square: ShiftSquare) -> Set { 82 | Set(squares.filter({ $0.position.isBetween(a: square.position, b: emptyPosition) })) 83 | } 84 | 85 | public func drag(_ square: ShiftSquare, amount: Double) { 86 | activeSquare = square 87 | dragAmount = min(max(0, amount), 1) 88 | } 89 | 90 | public func dragEnded(finalAmount: Double? = nil) { 91 | if let finalAmount = finalAmount { 92 | dragAmount = finalAmount 93 | } 94 | if let activeSquare = activeSquare { 95 | if dragAmount >= 0.5 { 96 | moves += 1 97 | shift(activeSquare) 98 | } else if pushedAmount >= 0.5 { 99 | moves += 1 100 | pushedSquares.forEach({ 101 | squares.update(with: $0.shifting(dragDirection)) 102 | }) 103 | emptyPosition = activeSquare.position.shifting(direction: dragDirection) 104 | } 105 | } 106 | activeSquare = nil 107 | dragAmount = 0 108 | pushedAmount = 0 109 | } 110 | 111 | func shift(_ square: ShiftSquare) { 112 | let direction = shiftableDirection(of: square) 113 | let pushableSquares = self.pushableSquares(whenShifting: square) 114 | squares.update(with: square.shifting(direction)) 115 | pushableSquares.forEach({ 116 | squares.update(with: $0.shifting(direction)) 117 | }) 118 | if direction != .none { 119 | emptyPosition = square.position 120 | } 121 | } 122 | 123 | func randomShift() { 124 | if let square = squares.filter({ $0.position.sharesColumn(with: emptyPosition) || $0.position.sharesRow(with: emptyPosition) }).randomElement() { 125 | shift(square) 126 | } 127 | } 128 | 129 | public func shuffleBoard() { 130 | for i in 0...100 { 131 | if i % 2 == 0 { 132 | // shift vertical 133 | if let square = squares.filter({ $0.position.sharesColumn(with: emptyPosition) }).randomElement() { 134 | shift(square) 135 | } 136 | } else { 137 | // shift horizontal 138 | if let square = squares.filter({ $0.position.sharesRow(with: emptyPosition) }).randomElement() { 139 | shift(square) 140 | } 141 | } 142 | } 143 | } 144 | } 145 | 146 | -------------------------------------------------------------------------------- /ShiftUI/Model/ShiftSquare.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShiftSquare.swift 3 | // ShiftUI 4 | // 5 | // Created by Ryan Lintott on 2021-02-19. 6 | // 7 | 8 | import Foundation 9 | 10 | struct ShiftSquare: Comparable, UniqueById { 11 | var position: Position 12 | let solvedPosition: Position 13 | 14 | init(position: Position? = nil, solvedPosition: Position) { 15 | self.position = position ?? solvedPosition 16 | self.solvedPosition = solvedPosition 17 | } 18 | 19 | var isSolved: Bool { 20 | position == solvedPosition 21 | } 22 | 23 | func changingPosition(to newPosition: Position) -> ShiftSquare { 24 | ShiftSquare(position: newPosition, solvedPosition: solvedPosition) 25 | } 26 | 27 | mutating func shift(_ direction: Direction) { 28 | self = changingPosition(to: position.shifting(direction: direction)) 29 | } 30 | 31 | func shifting(_ direction: Direction) -> ShiftSquare { 32 | changingPosition(to: position.shifting(direction: direction)) 33 | } 34 | 35 | var id: Position { 36 | solvedPosition 37 | } 38 | 39 | var log: String { 40 | """ 41 | number \(position.row) \(position.column) 42 | position \(solvedPosition.row) \(solvedPosition.column) 43 | """ 44 | } 45 | 46 | static func < (lhs: ShiftSquare, rhs: ShiftSquare) -> Bool { 47 | lhs.position < rhs.position 48 | } 49 | } 50 | 51 | extension Collection where Element == ShiftSquare { 52 | var isSolved: Bool { 53 | self.allSatisfy({ $0.isSolved }) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /ShiftUI/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /ShiftUI/ShiftUIApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShiftUIApp.swift 3 | // ShiftUI 4 | // 5 | // Created by Ryan Lintott on 2021-02-19. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct ShiftUIApp: App { 12 | var body: some Scene { 13 | WindowGroup { 14 | ContentView() 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ShiftUI/View/AppIconView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppIconView.swift 3 | // ShiftUI 4 | // 5 | // Created by Ryan Lintott on 2021-02-21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct AppIcon: View { 11 | let size: CGFloat = 1024 12 | let roundingPercentage: CGFloat = 0.2237 13 | var cornerRadius: CGFloat { 14 | size * roundingPercentage 15 | } 16 | 17 | var shape: some InsettableShape { 18 | RoundedRectangle(cornerRadius: cornerRadius, style: .continuous) 19 | } 20 | 21 | var body: some View { 22 | ZStack { 23 | shape 24 | // .inset(by: size * 0.09) 25 | .neumorphicShadow(height: -20) 26 | 27 | Image(systemName: "shift") 28 | .font(.system(size: 540)) 29 | .foregroundColor(.grayText) 30 | .neumorphicShadow(height: 10) 31 | } 32 | .frame(width: size, height: size) 33 | .background(Color.offWhite.edgesIgnoringSafeArea(.all)) 34 | 35 | } 36 | } 37 | 38 | struct AppIcon_Previews: PreviewProvider { 39 | static var previews: some View { 40 | AppIcon() 41 | .previewLayout(.sizeThatFits) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /ShiftUI/View/BigTitleView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BigTitleView.swift 3 | // ShiftUI 4 | // 5 | // Created by Ryan Lintott on 2021-02-21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct BigTitleView: View { 11 | let text: String 12 | 13 | var body: some View { 14 | Text("ShiftUI") 15 | .font(.system(size: 60)) 16 | .fontWeight(.black) 17 | .minimumScaleFactor(0.5) 18 | .neumorphicShadow(height: 3) 19 | } 20 | } 21 | 22 | struct BigTitleView_Previews: PreviewProvider { 23 | static var previews: some View { 24 | BigTitleView(text: "ShiftUI") 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /ShiftUI/View/BoardView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BoardView.swift 3 | // ShiftUI 4 | // 5 | // Created by Ryan Lintott on 2021-02-19. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct BoardView: View { 11 | @EnvironmentObject var board: ShiftBoard 12 | @State private var visibleTileNumberThreshold: Int = 0 13 | 14 | let availableSize: CGSize 15 | 16 | var delayPerTile: Double { 17 | AnimationStyle.neumorphic.duration / Double(board.squares.count) 18 | } 19 | var speedPerTile: Double { 20 | delayPerTile * 4 21 | } 22 | 23 | var tileSize: CGFloat { 24 | min(availableSize.width / CGFloat(board.totalColumns), availableSize.height / CGFloat(board.totalRows)) 25 | } 26 | 27 | var gridColumns: [GridItem] { 28 | Array.init( 29 | repeating: GridItem(.fixed(tileSize), spacing: 0), 30 | count: board.totalColumns 31 | ) 32 | } 33 | 34 | var body: some View { 35 | VStack { 36 | Spacer(minLength: 0) 37 | 38 | LazyVGrid(columns: gridColumns, alignment: .center, spacing: 0) { 39 | ForEach(board.positions.sorted()) { position in 40 | VStack { 41 | switch position { 42 | case let .occupied(square): 43 | if board.number(of: square) <= visibleTileNumberThreshold { 44 | ShiftableView(square: square, tileSize: tileSize) { isShifting in 45 | TileView(square: square) 46 | } 47 | } 48 | case .empty: 49 | Color.clear 50 | } 51 | } 52 | .frame(width: tileSize, height: tileSize) 53 | } 54 | } 55 | } 56 | .onAppear { 57 | for i in 0...board.squares.count { 58 | withAnimation(Animation.easeInOut(duration: speedPerTile).delay(Double(i) * delayPerTile + AnimationStyle.neumorphic.duration)) { 59 | visibleTileNumberThreshold = i 60 | } 61 | } 62 | } 63 | .onDisappear { 64 | for i in stride(from: board.squares.count, to: 0, by: -1) { 65 | withAnimation(Animation.easeInOut(duration: speedPerTile).delay(Double(i) * delayPerTile)) { 66 | visibleTileNumberThreshold = i 67 | } 68 | } 69 | } 70 | } 71 | 72 | 73 | } 74 | 75 | struct BoardView_Previews: PreviewProvider { 76 | static var previews: some View { 77 | GeometryReader { proxy in 78 | BoardView(availableSize: proxy.size) 79 | .environmentObject(ShiftBoard(level: .hard)) 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /ShiftUI/View/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // ShiftUI 4 | // 5 | // Created by Ryan Lintott on 2021-02-21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ContentView: View { 11 | @StateObject var viewManager: ViewManager = ViewManager() 12 | @State private var difficultyLevel: Level = .medium 13 | 14 | var body: some View { 15 | ZStack { 16 | Color.offWhite.edgesIgnoringSafeArea(.all) 17 | 18 | VStack { 19 | BigTitleView(text: "ShiftUI") 20 | 21 | switch viewManager.activeView { 22 | case .home: 23 | WelcomeView(difficultyLevel: $difficultyLevel) 24 | case .game: 25 | GameView(level: difficultyLevel) 26 | case .none: 27 | Color.clear 28 | } 29 | } 30 | } 31 | .overlay( 32 | Group { 33 | if viewManager.activeView == .game { 34 | NeumorphicButton(height: 2, pressedHeight: -2) { 35 | viewManager.changeViewNeumorphic(to: .home) 36 | } label: { 37 | Image(systemName: "xmark") 38 | .font(Font.title.weight(.black)) 39 | } 40 | 41 | // Button { 42 | // viewManager.changeViewNeumorphic(to: .home) 43 | // } label: { 44 | // Image(systemName: "xmark") 45 | // .font(Font.title.weight(.black)) 46 | // } 47 | // .buttonStyle(NeumorphicButtonStyle(height: 2, pressedHeight: -2)) 48 | .padding(10) 49 | } 50 | } 51 | 52 | , alignment: .topTrailing 53 | ) 54 | .foregroundColor(.grayText) 55 | .environmentObject(viewManager) 56 | } 57 | } 58 | 59 | struct ContentView_Previews: PreviewProvider { 60 | static var previews: some View { 61 | ContentView() 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /ShiftUI/View/GameView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // ShiftUI 4 | // 5 | // Created by Ryan Lintott on 2021-02-19. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct GameView: View { 11 | @EnvironmentObject var viewManager: ViewManager 12 | @StateObject var board = ShiftBoard() 13 | @State private var isShowingResult = false 14 | @State private var isShowingBoard = false 15 | 16 | let level: Level 17 | 18 | var body: some View { 19 | ZStack { 20 | Color.clear 21 | 22 | if isShowingResult { 23 | ResultView(score: board.moves) 24 | } else { 25 | VStack { 26 | HStack { 27 | Text("Moves:") 28 | Spacer() 29 | Text("\(board.moves)") 30 | .animation(nil) 31 | } 32 | .font(.title) 33 | .padding(.horizontal) 34 | .padding(.vertical, 5) 35 | .background( 36 | RoundedRectangle(cornerRadius: 10) 37 | .neumorphicShadow(height: 2) 38 | ) 39 | .padding(.horizontal, 30) 40 | .padding(.all, 10) 41 | 42 | GeometryReader { proxy in 43 | if isShowingBoard { 44 | BoardView(availableSize: proxy.size) 45 | .environmentObject(board) 46 | } 47 | } 48 | } 49 | .padding() 50 | } 51 | } 52 | .onAppear(perform: setupBoard) 53 | .onChange(of: board.isSolved) { isSolved in 54 | if isSolved { 55 | showResults() 56 | } 57 | } 58 | } 59 | 60 | func setupBoard() { 61 | board.initBoard(level: level) 62 | board.shuffleBoard() 63 | DispatchQueue.main.asyncAfter(deadline: .now() + AnimationStyle.neumorphic.duration) { 64 | isShowingBoard = true 65 | } 66 | } 67 | 68 | func showResults() { 69 | withAnimation(AnimationStyle.neumorphic.animation.delay(1)) { 70 | isShowingBoard = false 71 | } 72 | withAnimation(AnimationStyle.neumorphic.animation.delay(1 + AnimationStyle.neumorphic.duration)) { 73 | isShowingResult = true 74 | } 75 | } 76 | } 77 | 78 | struct GameView_Previews: PreviewProvider { 79 | static var previews: some View { 80 | GameView(level: .hard) 81 | } 82 | } 83 | 84 | -------------------------------------------------------------------------------- /ShiftUI/View/NeumorphicButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NeumorphicButton.swift 3 | // ShiftUI 4 | // 5 | // Created by Ryan Lintott on 2021-02-21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct NeumorphicButton: View { 11 | let height: CGFloat 12 | let pressedHeight: CGFloat 13 | let action: () -> Void 14 | let label: () -> Content 15 | 16 | @State private var isShowingButton: Bool = false 17 | 18 | var body: some View { 19 | Button(action: action) { 20 | label() 21 | .opacity(isShowingButton ? 1 : 0) 22 | } 23 | .buttonStyle(NeumorphicButtonStyle(isActive: isShowingButton, height: height, pressedHeight: pressedHeight)) 24 | .onAppear { 25 | withAnimation(AnimationStyle.neumorphic.animation) { 26 | isShowingButton = true 27 | } 28 | } 29 | .onDisappear { 30 | withAnimation(AnimationStyle.neumorphic.animation) { 31 | isShowingButton = true 32 | } 33 | } 34 | } 35 | } 36 | 37 | struct NeumorphicButtonStyle: ButtonStyle { 38 | let isActive: Bool 39 | let height: CGFloat 40 | let pressedHeight: CGFloat 41 | 42 | init(isActive: Bool = true, height: CGFloat = 5, pressedHeight: CGFloat = -4) { 43 | self.isActive = isActive 44 | self.height = height 45 | self.pressedHeight = pressedHeight 46 | } 47 | 48 | func makeBody(configuration: Configuration) -> some View { 49 | configuration.label 50 | .neumorphicShadow(isActive: isActive, height: configuration.isPressed ? pressedHeight : height) 51 | } 52 | } 53 | 54 | struct NeumorphicButton_Previews: PreviewProvider { 55 | static var previews: some View { 56 | Button { 57 | // do nothing 58 | } label: { 59 | Image(systemName: "xmark") 60 | .font(Font.title.weight(.black)) 61 | } 62 | .buttonStyle(NeumorphicButtonStyle()) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /ShiftUI/View/NeumorphicShadow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NeumorphicShadow.swift 3 | // ShiftUI 4 | // 5 | // Created by Zoha on 2/20/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | extension Shape { 11 | func neumorphicShadow(isActive: Bool, color: Color = .offWhite, height: CGFloat = 5) -> some View { 12 | self 13 | .neumorphicShadow(color: color, height: (isActive ? 1 : 0) * height) 14 | } 15 | 16 | func neumorphicShadow(color: Color = .offWhite, height: CGFloat = 5) -> some View { 17 | self 18 | .fill(color) 19 | .overlay( 20 | ZStack { 21 | self 22 | .stroke(Color.black.opacity(0.43), lineWidth: height.magnitude) 23 | .blur(radius: height.magnitude) 24 | .offset(x: -height / 2, y: -height / 2) 25 | .mask( 26 | self 27 | .fill(LinearGradient(Color.black, Color.clear)) 28 | ) 29 | 30 | self 31 | .stroke(Color.white, lineWidth: height.magnitude * 2) 32 | .blur(radius: height.magnitude) 33 | .offset(x: height / 2, y: height / 2) 34 | .mask( 35 | self 36 | .fill(LinearGradient(Color.clear, Color.black)) 37 | ) 38 | } 39 | .opacity(height < 0 ? 1 : 0) 40 | ) 41 | .shadow(color: Color.black.opacity(0.2).opacity(height >= 0 ? 1 : 0), radius: height.magnitude, x: height, y: height) 42 | .shadow(color: Color.white.opacity(0.7).opacity(height >= 0 ? 1 : 0), radius: height.magnitude, x: -height, y: -height) 43 | } 44 | } 45 | 46 | extension View { 47 | func neumorphicShadow(isActive: Bool, height: CGFloat = 5) -> some View { 48 | self 49 | .neumorphicShadow(height: (isActive ? 1 : 0) * height) 50 | } 51 | 52 | func neumorphicShadow(height: CGFloat = 5) -> some View { 53 | self 54 | .overlay( 55 | ZStack { 56 | Color.black.opacity(0.1) 57 | .mask( 58 | Color.white 59 | .overlay( 60 | Color.black 61 | .mask( 62 | self 63 | .offset(x: -height * 1.5, y: -height * 1.5) 64 | .blur(radius: height.magnitude) 65 | ) 66 | ) 67 | .drawingGroup() 68 | .luminanceToAlpha() 69 | ) 70 | .blendMode(.darken) 71 | 72 | Color.white.opacity(0.4) 73 | .mask( 74 | Color.white 75 | .overlay( 76 | Color.black 77 | .mask( 78 | self 79 | .offset(x: height * 1.5, y: height * 1.5) 80 | .blur(radius: height.magnitude) 81 | ) 82 | ) 83 | .drawingGroup() 84 | .luminanceToAlpha() 85 | ) 86 | .blendMode(.screen) 87 | } 88 | .mask(self) 89 | .opacity(height < 0 ? 1 : 0) 90 | ) 91 | .shadow(color: Color.black.opacity(0.2).opacity(height >= 0 ? 1 : 0), radius: height.magnitude, x: height, y: height) 92 | .shadow(color: Color.white.opacity(0.7).opacity(height >= 0 ? 1 : 0), radius: height.magnitude, x: -height, y: -height) 93 | } 94 | } 95 | 96 | struct NeumorphicShadow_Previews: PreviewProvider { 97 | struct PreviewData: View { 98 | @State private var height: CGFloat = 0 99 | 100 | var body: some View { 101 | VStack { 102 | ZStack { 103 | Capsule() 104 | .fill(Color.offWhite) 105 | .frame(width: 50) 106 | 107 | Capsule() 108 | .fill(Color.offWhite) 109 | .frame(height: 50) 110 | } 111 | .clipped() 112 | .neumorphicShadow(height: height) 113 | .padding(50) 114 | 115 | 116 | Text("Hello World") 117 | .font(.system(size: 60)) 118 | .bold() 119 | .foregroundColor(Color.offWhite) 120 | .neumorphicShadow(height: height / 4) 121 | 122 | Image(systemName: "hand.thumbsup.fill") 123 | .font(.system(size: 60)) 124 | .foregroundColor(.offWhite) 125 | .neumorphicShadow(height: height / 4) 126 | 127 | Circle() 128 | .neumorphicShadow(height: height) 129 | .padding(50) 130 | 131 | Slider(value: $height, in: -20...20) 132 | .padding() 133 | } 134 | .background(Color.offWhite.edgesIgnoringSafeArea(.all)) 135 | } 136 | } 137 | 138 | static var previews: some View { 139 | PreviewData() 140 | } 141 | } 142 | 143 | -------------------------------------------------------------------------------- /ShiftUI/View/ResultView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ResultView.swift 3 | // ShiftUI 4 | // 5 | // Created by Zoha on 2/20/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ResultView: View { 11 | @EnvironmentObject var viewManager: ViewManager 12 | let score: Int 13 | 14 | var body: some View { 15 | VStack { 16 | Spacer() 17 | 18 | Text("Winner Winner\nChicken Dinner...!") 19 | .font(.largeTitle) 20 | .fontWeight(.medium) 21 | .multilineTextAlignment(.center) 22 | .animation(AnimationStyle.textFade.animation) 23 | 24 | Spacer() 25 | 26 | SuccessView(score: score) 27 | 28 | Spacer() 29 | } 30 | .padding() 31 | } 32 | } 33 | 34 | struct ResultView_Previews: PreviewProvider { 35 | static var previews: some View { 36 | ResultView(score: 343) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /ShiftUI/View/ShiftableView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShiftableView.swift 3 | // ShiftUI 4 | // 5 | // Created by Ryan Lintott on 2021-02-20. 6 | // 7 | 8 | import SwiftUI 9 | import CoreHaptics 10 | 11 | struct ShiftableView: View { 12 | @EnvironmentObject var board: ShiftBoard 13 | 14 | let square: ShiftSquare 15 | let tileSize: CGFloat 16 | let content: (Bool) -> Content 17 | 18 | @State private var predictedDragOffset: CGSize = .zero 19 | @GestureState private var isDragging: Bool = false 20 | @State private var engine: CHHapticEngine? 21 | 22 | var body: some View { 23 | content(isDragging) 24 | .offset(tileOffset) 25 | .gesture(shiftGesture) 26 | .onChange(of: isDragging) { isDragging in 27 | if isDragging { 28 | prepareHaptics() 29 | } else { 30 | withAnimation(Animation.spring(response: 0.1, dampingFraction: 0.95, blendDuration: 0.5)) { 31 | board.dragEnded(finalAmount: dragAmount(predictedDragOffset)) 32 | } 33 | playHaptic() 34 | } 35 | predictedDragOffset = .zero 36 | } 37 | } 38 | 39 | func prepareHaptics() { 40 | guard CHHapticEngine.capabilitiesForHardware().supportsHaptics else { return } 41 | 42 | do { 43 | self.engine = try CHHapticEngine() 44 | try engine?.start() 45 | } catch { 46 | print("There was an error creating the engine: \(error.localizedDescription)") 47 | } 48 | } 49 | 50 | func playHaptic() { 51 | // make sure that the device supports haptics 52 | guard CHHapticEngine.capabilitiesForHardware().supportsHaptics else { return } 53 | var events = [CHHapticEvent]() 54 | 55 | // create one intense, sharp tap 56 | let intensity = CHHapticEventParameter(parameterID: .hapticIntensity, value: 0.5) 57 | let sharpness = CHHapticEventParameter(parameterID: .hapticSharpness, value: 1) 58 | let event = CHHapticEvent(eventType: .hapticTransient, parameters: [intensity, sharpness], relativeTime: 0) 59 | events.append(event) 60 | 61 | // convert those events into a pattern and play it immediately 62 | do { 63 | let pattern = try CHHapticPattern(events: events, parameters: []) 64 | let player = try engine?.makePlayer(with: pattern) 65 | try player?.start(atTime: 0) 66 | } catch { 67 | print("Failed to play pattern: \(error.localizedDescription).") 68 | } 69 | } 70 | 71 | var tileOffset: CGSize { 72 | if square == board.activeSquare { 73 | return tileOffset(amount: board.dragAmount) 74 | } else if board.pushedSquares.contains(square) { 75 | return tileOffset(amount: board.pushedAmount) 76 | } else { 77 | return .zero 78 | } 79 | } 80 | 81 | func tileOffset(amount: Double) -> CGSize { 82 | let offset = CGFloat(amount) * tileSize 83 | switch board.dragDirection { 84 | case .up: 85 | return CGSize(width: 0, height: -offset) 86 | case .down: 87 | return CGSize(width: 0, height: offset) 88 | case .left: 89 | return CGSize(width: -offset, height: 0) 90 | case .right: 91 | return CGSize(width: offset, height: 0) 92 | case .none: 93 | return .zero 94 | } 95 | } 96 | 97 | var shiftGesture: some Gesture { 98 | DragGesture(minimumDistance: 0) 99 | .updating($isDragging) { value, gestureState, transaction in 100 | gestureState = true 101 | } 102 | .onChanged { value in 103 | predictedDragOffset = value.predictedEndTranslation 104 | board.drag(square, amount: dragAmount(value.translation)) 105 | } 106 | } 107 | 108 | func dragAmount(_ translation: CGSize) -> Double { 109 | Double(dragOffset(translation) / tileSize) 110 | } 111 | 112 | func dragOffset(_ translation: CGSize) -> CGFloat { 113 | switch board.shiftableDirection(of: square) { 114 | case .up: 115 | return -translation.height 116 | case .down: 117 | return translation.height 118 | case .left: 119 | return -translation.width 120 | case .right: 121 | return translation.width 122 | case .none: 123 | return .zero 124 | } 125 | } 126 | 127 | } 128 | -------------------------------------------------------------------------------- /ShiftUI/View/SuccessView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SuccessView.swift 3 | // ShiftUI 4 | // 5 | // Created by Zoha on 2/20/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct SuccessView: View { 11 | @State private var scale: CGFloat = 0.8 12 | @State private var opacity: Double = 1 13 | 14 | @State private var isShowingText: Bool = false 15 | @State private var isNeumorphic: Bool = false 16 | 17 | let score: Int 18 | 19 | var body: some View { 20 | VStack { 21 | Text("Your score") 22 | 23 | Text("\(score)") 24 | .font(.largeTitle) 25 | .fontWeight(.medium) 26 | .foregroundColor(.pink) 27 | } 28 | .frame(width: 200, height: 200) 29 | .opacity(isShowingText ? 1 : 0) 30 | .background( 31 | Circle().neumorphicShadow(isActive: isNeumorphic) 32 | ) 33 | .background( 34 | Circle() 35 | .neumorphicShadow(isActive: isNeumorphic) 36 | .scaleEffect(scale) 37 | .opacity(opacity) 38 | ) 39 | .onAppear { 40 | withAnimation(AnimationStyle.textFade.animation) { 41 | isShowingText = true 42 | } 43 | withAnimation(AnimationStyle.neumorphic.animation.delay(AnimationStyle.textFade.duration / 2)) { 44 | isNeumorphic = true 45 | } 46 | withAnimation( 47 | Animation 48 | .easeIn(duration: 2) 49 | .repeatForever(autoreverses: false) 50 | .delay(AnimationStyle.textFade.duration / 2) 51 | ) { 52 | opacity = 0.0 53 | scale = 3 54 | } 55 | } 56 | } 57 | } 58 | 59 | struct SuccessView_Previews: PreviewProvider { 60 | static var previews: some View { 61 | ZStack { 62 | Color.offWhite.edgesIgnoringSafeArea(.all) 63 | SuccessView(score: 232) 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /ShiftUI/View/TileButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TileView.swift 3 | // ShiftUI 4 | // 5 | // Created by Zoha on 2/20/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct TileButton: View { 11 | let data: Int 12 | let action: () -> Void 13 | 14 | var body: some View { 15 | Button(action: action) { 16 | Text("\(data)") 17 | } 18 | .buttonStyle(TileButtonStyle(cornerRadius: 8)) 19 | } 20 | } 21 | 22 | struct TileButtonStyle: ButtonStyle { 23 | let cornerRadius: CGFloat 24 | let height: CGFloat 25 | let pressedHeight: CGFloat 26 | 27 | init(cornerRadius: CGFloat = 5, height: CGFloat = 5, pressedHeight: CGFloat = -4) { 28 | self.cornerRadius = cornerRadius 29 | self.height = height 30 | self.pressedHeight = pressedHeight 31 | } 32 | 33 | func makeBody(configuration: Configuration) -> some View { 34 | ZStack { 35 | RoundedRectangle(cornerRadius: cornerRadius) 36 | .neumorphicShadow(height: configuration.isPressed ? pressedHeight : height) 37 | 38 | configuration.label 39 | } 40 | } 41 | } 42 | 43 | struct TileButton_Previews: PreviewProvider { 44 | static var previews: some View { 45 | ZStack { 46 | Color.offWhite.edgesIgnoringSafeArea(.all) 47 | 48 | TileButton(data: 2, action: {}) 49 | .frame(width: 50, height: 50) 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /ShiftUI/View/TileView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TileView.swift 3 | // ShiftUI 4 | // 5 | // Created by Ryan Lintott on 2021-02-21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct TileView: View { 11 | @EnvironmentObject var board: ShiftBoard 12 | 13 | let square: ShiftSquare 14 | 15 | @State private var isVisible: Bool = false 16 | 17 | var number: Int { 18 | board.number(of: square) 19 | } 20 | 21 | var neumorphicHeight: CGFloat { 22 | board.isSolved || board.shiftingSquares.contains(square) ? -4 : 4 23 | } 24 | 25 | var body: some View { 26 | RoundedRectangle(cornerRadius: 5) 27 | .neumorphicShadow(isActive: isVisible, height: neumorphicHeight) 28 | .overlay( 29 | Text("\(board.number(of: square))") 30 | .opacity(isVisible ? 1 : 0) 31 | ) 32 | .padding(4) 33 | .onAppear { 34 | isVisible = true 35 | } 36 | .onDisappear { 37 | isVisible = false 38 | } 39 | } 40 | } 41 | 42 | struct TileView_Previews: PreviewProvider { 43 | static var previews: some View { 44 | TileView(square: ShiftSquare(solvedPosition: Position(row: 1, column: 1))) 45 | .environmentObject(ShiftBoard(level: .easy)) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /ShiftUI/View/WelcomeView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WelcomeView.swift 3 | // ShiftUI 4 | // 5 | // Created by Zoha on 2/20/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct WelcomeView: View { 11 | @EnvironmentObject var viewManager: ViewManager 12 | @Binding var difficultyLevel: Level 13 | 14 | var body: some View { 15 | VStack { 16 | HStack { 17 | Text("Look again it's") 18 | Text("ShiftUI").fontWeight(.bold) 19 | Text("not SwiftUI 👻") 20 | } 21 | .font(.caption) 22 | 23 | Spacer() 24 | 25 | Text("Select difficulty level") 26 | .font(.title) 27 | 28 | HStack(spacing: 20) { 29 | ForEach(Level.allCases) { level in 30 | Text(level.label) 31 | .padding() 32 | .background( 33 | RoundedRectangle(cornerRadius: 10) 34 | .neumorphicShadow(height: difficultyLevel == level ? -4 : 4) 35 | ) 36 | .onTapGesture { 37 | difficultyLevel = level 38 | } 39 | 40 | } 41 | } 42 | 43 | Spacer() 44 | 45 | 46 | Button { 47 | viewManager.changeViewNeumorphic(to: .game) 48 | } label: { 49 | Text("Start Game") 50 | .font(.title) 51 | } 52 | .buttonStyle(TileButtonStyle(cornerRadius: 10)) 53 | .frame(height: 60) 54 | .padding(.horizontal, 50) 55 | 56 | Spacer() 57 | 58 | } 59 | .padding() 60 | } 61 | } 62 | 63 | struct WelcomeView_Previews: PreviewProvider { 64 | static var previews: some View { 65 | WelcomeView(difficultyLevel: .constant(.medium)) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /ShiftUI/ViewManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewManager.swift 3 | // ShiftUI 4 | // 5 | // Created by Ryan Lintott on 2021-02-21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | enum ViewState { 11 | case none, home, game 12 | } 13 | 14 | class ViewManager: ObservableObject { 15 | @Published private(set) var activeView: ViewState = .home 16 | 17 | func changeViewNeumorphic(to viewState: ViewState) { 18 | withAnimation(AnimationStyle.neumorphic.animation) { 19 | activeView = .none 20 | } 21 | withAnimation(AnimationStyle.neumorphic.animation.delay(AnimationStyle.neumorphic.duration)) { 22 | self.activeView = viewState 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /privacy-policy.md: -------------------------------------------------------------------------------- 1 | # Privacy Policy 2 | 3 | ShiftUI does not log or share any of your information. 4 | --------------------------------------------------------------------------------