├── TodoList.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcuserdata │ └── nicksarno.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist └── TodoList ├── Assets.xcassets ├── AccentColor.colorset │ └── Contents.json ├── AppIcon.appiconset │ ├── Contents.json │ ├── Icon-App-20x20@1x.png │ ├── Icon-App-20x20@2x-1.png │ ├── Icon-App-20x20@2x.png │ ├── Icon-App-20x20@3x.png │ ├── Icon-App-29x29@1x.png │ ├── Icon-App-29x29@2x-2.png │ ├── Icon-App-29x29@2x.png │ ├── Icon-App-29x29@3x.png │ ├── Icon-App-40x40@1x.png │ ├── Icon-App-40x40@2x-1.png │ ├── Icon-App-40x40@2x.png │ ├── Icon-App-40x40@3x.png │ ├── Icon-App-60x60@2x.png │ ├── Icon-App-60x60@3x.png │ ├── Icon-App-76x76@1x.png │ ├── Icon-App-76x76@2x.png │ ├── Icon-App-83.5x83.5@2x.png │ └── ItunesArtwork@2x.png ├── Contents.json └── SecondaryAccentColor.colorset │ └── Contents.json ├── Info.plist ├── Models └── ItemModel.swift ├── Preview Content └── Preview Assets.xcassets │ └── Contents.json ├── TodoListApp.swift ├── ViewModels └── ListViewModel.swift └── Views ├── AddView.swift ├── LaunchScreen.storyboard ├── ListRowView.swift ├── ListView.swift └── NoItemsView.swift /TodoList.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 022EF16725EEE90900112224 /* TodoListApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 022EF16625EEE90900112224 /* TodoListApp.swift */; }; 11 | 022EF16B25EEE90C00112224 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 022EF16A25EEE90C00112224 /* Assets.xcassets */; }; 12 | 022EF16E25EEE90C00112224 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 022EF16D25EEE90C00112224 /* Preview Assets.xcassets */; }; 13 | 022EF17825EEEA1D00112224 /* ListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 022EF17725EEEA1D00112224 /* ListView.swift */; }; 14 | 022EF17D25EEEC0100112224 /* ListRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 022EF17C25EEEC0000112224 /* ListRowView.swift */; }; 15 | 022EF18025EEEDF000112224 /* AddView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 022EF17F25EEEDF000112224 /* AddView.swift */; }; 16 | 02F8C3E425EF085200BDAB62 /* ItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02F8C3E325EF085200BDAB62 /* ItemModel.swift */; }; 17 | 02F8C3EE25EFF83500BDAB62 /* ListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02F8C3ED25EFF83500BDAB62 /* ListViewModel.swift */; }; 18 | 02F8C3FE25F08D6500BDAB62 /* NoItemsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02F8C3FD25F08D6500BDAB62 /* NoItemsView.swift */; }; 19 | 02F8C40225F19FBE00BDAB62 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 02F8C40125F19FBE00BDAB62 /* LaunchScreen.storyboard */; }; 20 | /* End PBXBuildFile section */ 21 | 22 | /* Begin PBXFileReference section */ 23 | 022EF16325EEE90900112224 /* TodoList.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TodoList.app; sourceTree = BUILT_PRODUCTS_DIR; }; 24 | 022EF16625EEE90900112224 /* TodoListApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodoListApp.swift; sourceTree = ""; }; 25 | 022EF16A25EEE90C00112224 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 26 | 022EF16D25EEE90C00112224 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 27 | 022EF16F25EEE90C00112224 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 28 | 022EF17725EEEA1D00112224 /* ListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListView.swift; sourceTree = ""; }; 29 | 022EF17C25EEEC0000112224 /* ListRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRowView.swift; sourceTree = ""; }; 30 | 022EF17F25EEEDF000112224 /* AddView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddView.swift; sourceTree = ""; }; 31 | 02F8C3E325EF085200BDAB62 /* ItemModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemModel.swift; sourceTree = ""; }; 32 | 02F8C3ED25EFF83500BDAB62 /* ListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListViewModel.swift; sourceTree = ""; }; 33 | 02F8C3FD25F08D6500BDAB62 /* NoItemsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoItemsView.swift; sourceTree = ""; }; 34 | 02F8C40125F19FBE00BDAB62 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; 35 | /* End PBXFileReference section */ 36 | 37 | /* Begin PBXFrameworksBuildPhase section */ 38 | 022EF16025EEE90900112224 /* Frameworks */ = { 39 | isa = PBXFrameworksBuildPhase; 40 | buildActionMask = 2147483647; 41 | files = ( 42 | ); 43 | runOnlyForDeploymentPostprocessing = 0; 44 | }; 45 | /* End PBXFrameworksBuildPhase section */ 46 | 47 | /* Begin PBXGroup section */ 48 | 022EF15A25EEE90900112224 = { 49 | isa = PBXGroup; 50 | children = ( 51 | 022EF16525EEE90900112224 /* TodoList */, 52 | 022EF16425EEE90900112224 /* Products */, 53 | ); 54 | sourceTree = ""; 55 | }; 56 | 022EF16425EEE90900112224 /* Products */ = { 57 | isa = PBXGroup; 58 | children = ( 59 | 022EF16325EEE90900112224 /* TodoList.app */, 60 | ); 61 | name = Products; 62 | sourceTree = ""; 63 | }; 64 | 022EF16525EEE90900112224 /* TodoList */ = { 65 | isa = PBXGroup; 66 | children = ( 67 | 022EF16625EEE90900112224 /* TodoListApp.swift */, 68 | 02F8C3E225EF082E00BDAB62 /* Models */, 69 | 02F8C3EC25EFF80400BDAB62 /* ViewModels */, 70 | 022EF17625EEEA0500112224 /* Views */, 71 | 022EF16A25EEE90C00112224 /* Assets.xcassets */, 72 | 022EF16F25EEE90C00112224 /* Info.plist */, 73 | 022EF16C25EEE90C00112224 /* Preview Content */, 74 | ); 75 | path = TodoList; 76 | sourceTree = ""; 77 | }; 78 | 022EF16C25EEE90C00112224 /* Preview Content */ = { 79 | isa = PBXGroup; 80 | children = ( 81 | 022EF16D25EEE90C00112224 /* Preview Assets.xcassets */, 82 | ); 83 | path = "Preview Content"; 84 | sourceTree = ""; 85 | }; 86 | 022EF17625EEEA0500112224 /* Views */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | 022EF17725EEEA1D00112224 /* ListView.swift */, 90 | 022EF17C25EEEC0000112224 /* ListRowView.swift */, 91 | 022EF17F25EEEDF000112224 /* AddView.swift */, 92 | 02F8C3FD25F08D6500BDAB62 /* NoItemsView.swift */, 93 | 02F8C40125F19FBE00BDAB62 /* LaunchScreen.storyboard */, 94 | ); 95 | path = Views; 96 | sourceTree = ""; 97 | }; 98 | 02F8C3E225EF082E00BDAB62 /* Models */ = { 99 | isa = PBXGroup; 100 | children = ( 101 | 02F8C3E325EF085200BDAB62 /* ItemModel.swift */, 102 | ); 103 | path = Models; 104 | sourceTree = ""; 105 | }; 106 | 02F8C3EC25EFF80400BDAB62 /* ViewModels */ = { 107 | isa = PBXGroup; 108 | children = ( 109 | 02F8C3ED25EFF83500BDAB62 /* ListViewModel.swift */, 110 | ); 111 | path = ViewModels; 112 | sourceTree = ""; 113 | }; 114 | /* End PBXGroup section */ 115 | 116 | /* Begin PBXNativeTarget section */ 117 | 022EF16225EEE90900112224 /* TodoList */ = { 118 | isa = PBXNativeTarget; 119 | buildConfigurationList = 022EF17225EEE90C00112224 /* Build configuration list for PBXNativeTarget "TodoList" */; 120 | buildPhases = ( 121 | 022EF15F25EEE90900112224 /* Sources */, 122 | 022EF16025EEE90900112224 /* Frameworks */, 123 | 022EF16125EEE90900112224 /* Resources */, 124 | ); 125 | buildRules = ( 126 | ); 127 | dependencies = ( 128 | ); 129 | name = TodoList; 130 | productName = TodoList; 131 | productReference = 022EF16325EEE90900112224 /* TodoList.app */; 132 | productType = "com.apple.product-type.application"; 133 | }; 134 | /* End PBXNativeTarget section */ 135 | 136 | /* Begin PBXProject section */ 137 | 022EF15B25EEE90900112224 /* Project object */ = { 138 | isa = PBXProject; 139 | attributes = { 140 | LastSwiftUpdateCheck = 1230; 141 | LastUpgradeCheck = 1230; 142 | TargetAttributes = { 143 | 022EF16225EEE90900112224 = { 144 | CreatedOnToolsVersion = 12.3; 145 | }; 146 | }; 147 | }; 148 | buildConfigurationList = 022EF15E25EEE90900112224 /* Build configuration list for PBXProject "TodoList" */; 149 | compatibilityVersion = "Xcode 9.3"; 150 | developmentRegion = en; 151 | hasScannedForEncodings = 0; 152 | knownRegions = ( 153 | en, 154 | Base, 155 | ); 156 | mainGroup = 022EF15A25EEE90900112224; 157 | productRefGroup = 022EF16425EEE90900112224 /* Products */; 158 | projectDirPath = ""; 159 | projectRoot = ""; 160 | targets = ( 161 | 022EF16225EEE90900112224 /* TodoList */, 162 | ); 163 | }; 164 | /* End PBXProject section */ 165 | 166 | /* Begin PBXResourcesBuildPhase section */ 167 | 022EF16125EEE90900112224 /* Resources */ = { 168 | isa = PBXResourcesBuildPhase; 169 | buildActionMask = 2147483647; 170 | files = ( 171 | 02F8C40225F19FBE00BDAB62 /* LaunchScreen.storyboard in Resources */, 172 | 022EF16E25EEE90C00112224 /* Preview Assets.xcassets in Resources */, 173 | 022EF16B25EEE90C00112224 /* Assets.xcassets in Resources */, 174 | ); 175 | runOnlyForDeploymentPostprocessing = 0; 176 | }; 177 | /* End PBXResourcesBuildPhase section */ 178 | 179 | /* Begin PBXSourcesBuildPhase section */ 180 | 022EF15F25EEE90900112224 /* Sources */ = { 181 | isa = PBXSourcesBuildPhase; 182 | buildActionMask = 2147483647; 183 | files = ( 184 | 02F8C3E425EF085200BDAB62 /* ItemModel.swift in Sources */, 185 | 022EF18025EEEDF000112224 /* AddView.swift in Sources */, 186 | 02F8C3EE25EFF83500BDAB62 /* ListViewModel.swift in Sources */, 187 | 022EF16725EEE90900112224 /* TodoListApp.swift in Sources */, 188 | 022EF17825EEEA1D00112224 /* ListView.swift in Sources */, 189 | 02F8C3FE25F08D6500BDAB62 /* NoItemsView.swift in Sources */, 190 | 022EF17D25EEEC0100112224 /* ListRowView.swift in Sources */, 191 | ); 192 | runOnlyForDeploymentPostprocessing = 0; 193 | }; 194 | /* End PBXSourcesBuildPhase section */ 195 | 196 | /* Begin XCBuildConfiguration section */ 197 | 022EF17025EEE90C00112224 /* Debug */ = { 198 | isa = XCBuildConfiguration; 199 | buildSettings = { 200 | ALWAYS_SEARCH_USER_PATHS = NO; 201 | CLANG_ANALYZER_NONNULL = YES; 202 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 203 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 204 | CLANG_CXX_LIBRARY = "libc++"; 205 | CLANG_ENABLE_MODULES = YES; 206 | CLANG_ENABLE_OBJC_ARC = YES; 207 | CLANG_ENABLE_OBJC_WEAK = YES; 208 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 209 | CLANG_WARN_BOOL_CONVERSION = YES; 210 | CLANG_WARN_COMMA = YES; 211 | CLANG_WARN_CONSTANT_CONVERSION = YES; 212 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 213 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 214 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 215 | CLANG_WARN_EMPTY_BODY = YES; 216 | CLANG_WARN_ENUM_CONVERSION = YES; 217 | CLANG_WARN_INFINITE_RECURSION = YES; 218 | CLANG_WARN_INT_CONVERSION = YES; 219 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 220 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 221 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 222 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 223 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 224 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 225 | CLANG_WARN_STRICT_PROTOTYPES = YES; 226 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 227 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 228 | CLANG_WARN_UNREACHABLE_CODE = YES; 229 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 230 | COPY_PHASE_STRIP = NO; 231 | DEBUG_INFORMATION_FORMAT = dwarf; 232 | ENABLE_STRICT_OBJC_MSGSEND = YES; 233 | ENABLE_TESTABILITY = YES; 234 | GCC_C_LANGUAGE_STANDARD = gnu11; 235 | GCC_DYNAMIC_NO_PIC = NO; 236 | GCC_NO_COMMON_BLOCKS = YES; 237 | GCC_OPTIMIZATION_LEVEL = 0; 238 | GCC_PREPROCESSOR_DEFINITIONS = ( 239 | "DEBUG=1", 240 | "$(inherited)", 241 | ); 242 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 243 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 244 | GCC_WARN_UNDECLARED_SELECTOR = YES; 245 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 246 | GCC_WARN_UNUSED_FUNCTION = YES; 247 | GCC_WARN_UNUSED_VARIABLE = YES; 248 | IPHONEOS_DEPLOYMENT_TARGET = 14.3; 249 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 250 | MTL_FAST_MATH = YES; 251 | ONLY_ACTIVE_ARCH = YES; 252 | SDKROOT = iphoneos; 253 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 254 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 255 | }; 256 | name = Debug; 257 | }; 258 | 022EF17125EEE90C00112224 /* Release */ = { 259 | isa = XCBuildConfiguration; 260 | buildSettings = { 261 | ALWAYS_SEARCH_USER_PATHS = NO; 262 | CLANG_ANALYZER_NONNULL = YES; 263 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 264 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 265 | CLANG_CXX_LIBRARY = "libc++"; 266 | CLANG_ENABLE_MODULES = YES; 267 | CLANG_ENABLE_OBJC_ARC = YES; 268 | CLANG_ENABLE_OBJC_WEAK = YES; 269 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 270 | CLANG_WARN_BOOL_CONVERSION = YES; 271 | CLANG_WARN_COMMA = YES; 272 | CLANG_WARN_CONSTANT_CONVERSION = YES; 273 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 274 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 275 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 276 | CLANG_WARN_EMPTY_BODY = YES; 277 | CLANG_WARN_ENUM_CONVERSION = YES; 278 | CLANG_WARN_INFINITE_RECURSION = YES; 279 | CLANG_WARN_INT_CONVERSION = YES; 280 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 281 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 282 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 283 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 284 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 285 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 286 | CLANG_WARN_STRICT_PROTOTYPES = YES; 287 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 288 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 289 | CLANG_WARN_UNREACHABLE_CODE = YES; 290 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 291 | COPY_PHASE_STRIP = NO; 292 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 293 | ENABLE_NS_ASSERTIONS = NO; 294 | ENABLE_STRICT_OBJC_MSGSEND = YES; 295 | GCC_C_LANGUAGE_STANDARD = gnu11; 296 | GCC_NO_COMMON_BLOCKS = YES; 297 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 298 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 299 | GCC_WARN_UNDECLARED_SELECTOR = YES; 300 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 301 | GCC_WARN_UNUSED_FUNCTION = YES; 302 | GCC_WARN_UNUSED_VARIABLE = YES; 303 | IPHONEOS_DEPLOYMENT_TARGET = 14.3; 304 | MTL_ENABLE_DEBUG_INFO = NO; 305 | MTL_FAST_MATH = YES; 306 | SDKROOT = iphoneos; 307 | SWIFT_COMPILATION_MODE = wholemodule; 308 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 309 | VALIDATE_PRODUCT = YES; 310 | }; 311 | name = Release; 312 | }; 313 | 022EF17325EEE90C00112224 /* Debug */ = { 314 | isa = XCBuildConfiguration; 315 | buildSettings = { 316 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 317 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 318 | CODE_SIGN_STYLE = Automatic; 319 | DEVELOPMENT_ASSET_PATHS = "\"TodoList/Preview Content\""; 320 | DEVELOPMENT_TEAM = ""; 321 | ENABLE_PREVIEWS = YES; 322 | INFOPLIST_FILE = TodoList/Info.plist; 323 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 324 | LD_RUNPATH_SEARCH_PATHS = ( 325 | "$(inherited)", 326 | "@executable_path/Frameworks", 327 | ); 328 | PRODUCT_BUNDLE_IDENTIFIER = com.yourname.TodoList; 329 | PRODUCT_NAME = "$(TARGET_NAME)"; 330 | SWIFT_VERSION = 5.0; 331 | TARGETED_DEVICE_FAMILY = "1,2"; 332 | }; 333 | name = Debug; 334 | }; 335 | 022EF17425EEE90C00112224 /* Release */ = { 336 | isa = XCBuildConfiguration; 337 | buildSettings = { 338 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 339 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 340 | CODE_SIGN_STYLE = Automatic; 341 | DEVELOPMENT_ASSET_PATHS = "\"TodoList/Preview Content\""; 342 | DEVELOPMENT_TEAM = ""; 343 | ENABLE_PREVIEWS = YES; 344 | INFOPLIST_FILE = TodoList/Info.plist; 345 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 346 | LD_RUNPATH_SEARCH_PATHS = ( 347 | "$(inherited)", 348 | "@executable_path/Frameworks", 349 | ); 350 | PRODUCT_BUNDLE_IDENTIFIER = com.yourname.TodoList; 351 | PRODUCT_NAME = "$(TARGET_NAME)"; 352 | SWIFT_VERSION = 5.0; 353 | TARGETED_DEVICE_FAMILY = "1,2"; 354 | }; 355 | name = Release; 356 | }; 357 | /* End XCBuildConfiguration section */ 358 | 359 | /* Begin XCConfigurationList section */ 360 | 022EF15E25EEE90900112224 /* Build configuration list for PBXProject "TodoList" */ = { 361 | isa = XCConfigurationList; 362 | buildConfigurations = ( 363 | 022EF17025EEE90C00112224 /* Debug */, 364 | 022EF17125EEE90C00112224 /* Release */, 365 | ); 366 | defaultConfigurationIsVisible = 0; 367 | defaultConfigurationName = Release; 368 | }; 369 | 022EF17225EEE90C00112224 /* Build configuration list for PBXNativeTarget "TodoList" */ = { 370 | isa = XCConfigurationList; 371 | buildConfigurations = ( 372 | 022EF17325EEE90C00112224 /* Debug */, 373 | 022EF17425EEE90C00112224 /* Release */, 374 | ); 375 | defaultConfigurationIsVisible = 0; 376 | defaultConfigurationName = Release; 377 | }; 378 | /* End XCConfigurationList section */ 379 | }; 380 | rootObject = 022EF15B25EEE90900112224 /* Project object */; 381 | } 382 | -------------------------------------------------------------------------------- /TodoList.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /TodoList.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /TodoList.xcodeproj/xcuserdata/nicksarno.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | TodoList.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /TodoList/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.576", 9 | "green" : "0.106", 10 | "red" : "0.325" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "1.000", 27 | "green" : "0.216", 28 | "red" : "0.582" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /TodoList/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Icon-App-20x20@2x.png", 5 | "idiom" : "iphone", 6 | "scale" : "2x", 7 | "size" : "20x20" 8 | }, 9 | { 10 | "filename" : "Icon-App-20x20@3x.png", 11 | "idiom" : "iphone", 12 | "scale" : "3x", 13 | "size" : "20x20" 14 | }, 15 | { 16 | "filename" : "Icon-App-29x29@2x.png", 17 | "idiom" : "iphone", 18 | "scale" : "2x", 19 | "size" : "29x29" 20 | }, 21 | { 22 | "filename" : "Icon-App-29x29@3x.png", 23 | "idiom" : "iphone", 24 | "scale" : "3x", 25 | "size" : "29x29" 26 | }, 27 | { 28 | "filename" : "Icon-App-40x40@2x.png", 29 | "idiom" : "iphone", 30 | "scale" : "2x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "filename" : "Icon-App-40x40@3x.png", 35 | "idiom" : "iphone", 36 | "scale" : "3x", 37 | "size" : "40x40" 38 | }, 39 | { 40 | "filename" : "Icon-App-60x60@2x.png", 41 | "idiom" : "iphone", 42 | "scale" : "2x", 43 | "size" : "60x60" 44 | }, 45 | { 46 | "filename" : "Icon-App-60x60@3x.png", 47 | "idiom" : "iphone", 48 | "scale" : "3x", 49 | "size" : "60x60" 50 | }, 51 | { 52 | "filename" : "Icon-App-20x20@1x.png", 53 | "idiom" : "ipad", 54 | "scale" : "1x", 55 | "size" : "20x20" 56 | }, 57 | { 58 | "filename" : "Icon-App-20x20@2x-1.png", 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "20x20" 62 | }, 63 | { 64 | "filename" : "Icon-App-29x29@1x.png", 65 | "idiom" : "ipad", 66 | "scale" : "1x", 67 | "size" : "29x29" 68 | }, 69 | { 70 | "filename" : "Icon-App-29x29@2x-2.png", 71 | "idiom" : "ipad", 72 | "scale" : "2x", 73 | "size" : "29x29" 74 | }, 75 | { 76 | "filename" : "Icon-App-40x40@1x.png", 77 | "idiom" : "ipad", 78 | "scale" : "1x", 79 | "size" : "40x40" 80 | }, 81 | { 82 | "filename" : "Icon-App-40x40@2x-1.png", 83 | "idiom" : "ipad", 84 | "scale" : "2x", 85 | "size" : "40x40" 86 | }, 87 | { 88 | "filename" : "Icon-App-76x76@1x.png", 89 | "idiom" : "ipad", 90 | "scale" : "1x", 91 | "size" : "76x76" 92 | }, 93 | { 94 | "filename" : "Icon-App-76x76@2x.png", 95 | "idiom" : "ipad", 96 | "scale" : "2x", 97 | "size" : "76x76" 98 | }, 99 | { 100 | "filename" : "Icon-App-83.5x83.5@2x.png", 101 | "idiom" : "ipad", 102 | "scale" : "2x", 103 | "size" : "83.5x83.5" 104 | }, 105 | { 106 | "filename" : "ItunesArtwork@2x.png", 107 | "idiom" : "ios-marketing", 108 | "scale" : "1x", 109 | "size" : "1024x1024" 110 | } 111 | ], 112 | "info" : { 113 | "author" : "xcode", 114 | "version" : 1 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /TodoList/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SwiftfulThinking/SwiftUI-Todo-List-MVVM-UserDefaults/cde13a78963195053bd2562fe1633b1abd6086b8/TodoList/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /TodoList/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SwiftfulThinking/SwiftUI-Todo-List-MVVM-UserDefaults/cde13a78963195053bd2562fe1633b1abd6086b8/TodoList/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x-1.png -------------------------------------------------------------------------------- /TodoList/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SwiftfulThinking/SwiftUI-Todo-List-MVVM-UserDefaults/cde13a78963195053bd2562fe1633b1abd6086b8/TodoList/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /TodoList/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SwiftfulThinking/SwiftUI-Todo-List-MVVM-UserDefaults/cde13a78963195053bd2562fe1633b1abd6086b8/TodoList/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /TodoList/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SwiftfulThinking/SwiftUI-Todo-List-MVVM-UserDefaults/cde13a78963195053bd2562fe1633b1abd6086b8/TodoList/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /TodoList/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SwiftfulThinking/SwiftUI-Todo-List-MVVM-UserDefaults/cde13a78963195053bd2562fe1633b1abd6086b8/TodoList/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x-2.png -------------------------------------------------------------------------------- /TodoList/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SwiftfulThinking/SwiftUI-Todo-List-MVVM-UserDefaults/cde13a78963195053bd2562fe1633b1abd6086b8/TodoList/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /TodoList/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SwiftfulThinking/SwiftUI-Todo-List-MVVM-UserDefaults/cde13a78963195053bd2562fe1633b1abd6086b8/TodoList/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /TodoList/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SwiftfulThinking/SwiftUI-Todo-List-MVVM-UserDefaults/cde13a78963195053bd2562fe1633b1abd6086b8/TodoList/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /TodoList/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SwiftfulThinking/SwiftUI-Todo-List-MVVM-UserDefaults/cde13a78963195053bd2562fe1633b1abd6086b8/TodoList/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x-1.png -------------------------------------------------------------------------------- /TodoList/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SwiftfulThinking/SwiftUI-Todo-List-MVVM-UserDefaults/cde13a78963195053bd2562fe1633b1abd6086b8/TodoList/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /TodoList/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SwiftfulThinking/SwiftUI-Todo-List-MVVM-UserDefaults/cde13a78963195053bd2562fe1633b1abd6086b8/TodoList/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /TodoList/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SwiftfulThinking/SwiftUI-Todo-List-MVVM-UserDefaults/cde13a78963195053bd2562fe1633b1abd6086b8/TodoList/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /TodoList/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SwiftfulThinking/SwiftUI-Todo-List-MVVM-UserDefaults/cde13a78963195053bd2562fe1633b1abd6086b8/TodoList/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /TodoList/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SwiftfulThinking/SwiftUI-Todo-List-MVVM-UserDefaults/cde13a78963195053bd2562fe1633b1abd6086b8/TodoList/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /TodoList/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SwiftfulThinking/SwiftUI-Todo-List-MVVM-UserDefaults/cde13a78963195053bd2562fe1633b1abd6086b8/TodoList/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /TodoList/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SwiftfulThinking/SwiftUI-Todo-List-MVVM-UserDefaults/cde13a78963195053bd2562fe1633b1abd6086b8/TodoList/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /TodoList/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SwiftfulThinking/SwiftUI-Todo-List-MVVM-UserDefaults/cde13a78963195053bd2562fe1633b1abd6086b8/TodoList/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png -------------------------------------------------------------------------------- /TodoList/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /TodoList/Assets.xcassets/SecondaryAccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.319", 9 | "green" : "0.088", 10 | "red" : "0.581" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0.573", 27 | "green" : "0.186", 28 | "red" : "1.000" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /TodoList/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | Do Stuff 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UIApplicationSceneManifest 26 | 27 | UIApplicationSupportsMultipleScenes 28 | 29 | 30 | UIApplicationSupportsIndirectInputEvents 31 | 32 | UILaunchStoryboardName 33 | LaunchScreen 34 | UIRequiredDeviceCapabilities 35 | 36 | armv7 37 | 38 | UISupportedInterfaceOrientations 39 | 40 | UIInterfaceOrientationPortrait 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | UISupportedInterfaceOrientations~ipad 45 | 46 | UIInterfaceOrientationPortrait 47 | UIInterfaceOrientationPortraitUpsideDown 48 | UIInterfaceOrientationLandscapeLeft 49 | UIInterfaceOrientationLandscapeRight 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /TodoList/Models/ItemModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ItemModel.swift 3 | // TodoList 4 | // 5 | // Created by Nick Sarno on 3/2/21. 6 | // 7 | 8 | import Foundation 9 | 10 | // Immutable Struct has only 'let' constants 11 | struct ItemModel: Identifiable, Codable { 12 | let id: String 13 | let title: String 14 | let isCompleted: Bool 15 | 16 | init(id: String = UUID().uuidString, title: String, isCompleted: Bool) { 17 | self.id = id 18 | self.title = title 19 | self.isCompleted = isCompleted 20 | } 21 | 22 | func updateCompletion() -> ItemModel { 23 | return ItemModel(id: id, title: title, isCompleted: !isCompleted) 24 | } 25 | 26 | } 27 | 28 | -------------------------------------------------------------------------------- /TodoList/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /TodoList/TodoListApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TodoListApp.swift 3 | // TodoList 4 | // 5 | // Created by Nick Sarno on 3/2/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | /* 11 | MVVM Architecture 12 | 13 | Model - manages single data point 14 | View - manages the UI 15 | ViewModel - manages data (models) for views 16 | */ 17 | 18 | @main 19 | struct TodoListApp: App { 20 | 21 | @StateObject var listViewModel: ListViewModel = ListViewModel() 22 | 23 | var body: some Scene { 24 | WindowGroup { 25 | NavigationView { 26 | ListView() 27 | } 28 | .navigationViewStyle(StackNavigationViewStyle()) 29 | .environmentObject(listViewModel) 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /TodoList/ViewModels/ListViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ListViewModel.swift 3 | // TodoList 4 | // 5 | // Created by Nick Sarno on 3/3/21. 6 | // 7 | 8 | import Foundation 9 | 10 | /* 11 | CRUD FUNCTIONS: 12 | 13 | Create 14 | Read 15 | Update 16 | Delete 17 | */ 18 | 19 | class ListViewModel: ObservableObject { 20 | 21 | @Published var items: [ItemModel] = [] { 22 | didSet { 23 | saveItems() 24 | } 25 | } 26 | 27 | let itemsKey: String = "items_list" 28 | 29 | init() { 30 | getItems() 31 | } 32 | 33 | func getItems() { 34 | guard 35 | let data = UserDefaults.standard.data(forKey: itemsKey), 36 | let savedItems = try? JSONDecoder().decode([ItemModel].self, from: data) 37 | else { return } 38 | 39 | self.items = savedItems 40 | } 41 | 42 | func deleteItem(indexSet: IndexSet) { 43 | items.remove(atOffsets: indexSet) 44 | } 45 | 46 | func moveItem(from: IndexSet, to: Int) { 47 | items.move(fromOffsets: from, toOffset: to) 48 | } 49 | 50 | func addItem(title: String) { 51 | let newItem = ItemModel(title: title, isCompleted: false) 52 | items.append(newItem) 53 | } 54 | 55 | func updateItem(item: ItemModel) { 56 | if let index = items.firstIndex(where: { $0.id == item.id }) { 57 | items[index] = item.updateCompletion() 58 | } 59 | } 60 | 61 | func saveItems() { 62 | if let encodedData = try? JSONEncoder().encode(items) { 63 | UserDefaults.standard.set(encodedData, forKey: itemsKey) 64 | } 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /TodoList/Views/AddView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AddView.swift 3 | // TodoList 4 | // 5 | // Created by Nick Sarno on 3/2/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct AddView: View { 11 | 12 | // MARK: PROPERTIES 13 | 14 | @Environment(\.presentationMode) var presentationMode 15 | @EnvironmentObject var listViewModel: ListViewModel 16 | @State var textFieldText: String = "" 17 | 18 | @State var alertTitle: String = "" 19 | @State var showAlert: Bool = false 20 | 21 | // MARK: BODY 22 | 23 | var body: some View { 24 | ScrollView { 25 | VStack { 26 | TextField("Type something here...", text: $textFieldText) 27 | .padding(.horizontal) 28 | .frame(height: 55) 29 | .background(Color(UIColor.secondarySystemBackground)) 30 | .cornerRadius(10) 31 | 32 | Button(action: saveButtonPressed, label: { 33 | Text("Save".uppercased()) 34 | .foregroundColor(.white) 35 | .font(.headline) 36 | .frame(height: 55) 37 | .frame(maxWidth: .infinity) 38 | .background(Color.accentColor) 39 | .cornerRadius(10) 40 | }) 41 | } 42 | .padding(14) 43 | } 44 | .navigationTitle("Add an Item 🖊") 45 | .alert(isPresented: $showAlert, content: getAlert) 46 | } 47 | 48 | // MARK: FUNCTIONS 49 | 50 | func saveButtonPressed() { 51 | if textIsAppropriate() { 52 | listViewModel.addItem(title: textFieldText) 53 | presentationMode.wrappedValue.dismiss() 54 | } 55 | } 56 | 57 | func textIsAppropriate() -> Bool { 58 | if textFieldText.count < 3 { 59 | alertTitle = "Your new todo item must be at least 3 characters long!!! 😨😰😱" 60 | showAlert.toggle() 61 | return false 62 | } 63 | return true 64 | } 65 | 66 | func getAlert() -> Alert { 67 | return Alert(title: Text(alertTitle)) 68 | } 69 | 70 | } 71 | 72 | // MARK: PREVIEW 73 | 74 | struct AddView_Previews: PreviewProvider { 75 | static var previews: some View { 76 | Group { 77 | NavigationView { 78 | AddView() 79 | } 80 | .preferredColorScheme(.light) 81 | .environmentObject(ListViewModel()) 82 | NavigationView { 83 | AddView() 84 | } 85 | .preferredColorScheme(.dark) 86 | .environmentObject(ListViewModel()) 87 | 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /TodoList/Views/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /TodoList/Views/ListRowView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ListRowView.swift 3 | // TodoList 4 | // 5 | // Created by Nick Sarno on 3/2/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ListRowView: View { 11 | 12 | let item: ItemModel 13 | 14 | var body: some View { 15 | HStack { 16 | Image(systemName: item.isCompleted ? "checkmark.circle" : "circle") 17 | .foregroundColor(item.isCompleted ? .green : .red) 18 | Text(item.title) 19 | Spacer() 20 | } 21 | .font(.title2) 22 | .padding(.vertical, 8) 23 | } 24 | } 25 | 26 | struct ListRowView_Previews: PreviewProvider { 27 | 28 | static var item1 = ItemModel(title: "First item!", isCompleted: false) 29 | static var item2 = ItemModel(title: "Second Item.", isCompleted: true) 30 | 31 | static var previews: some View { 32 | Group { 33 | ListRowView(item: item1) 34 | ListRowView(item: item2) 35 | } 36 | .previewLayout(.sizeThatFits) 37 | 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /TodoList/Views/ListView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ListView.swift 3 | // TodoList 4 | // 5 | // Created by Nick Sarno on 3/2/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ListView: View { 11 | 12 | @EnvironmentObject var listViewModel: ListViewModel 13 | 14 | var body: some View { 15 | ZStack { 16 | if listViewModel.items.isEmpty { 17 | NoItemsView() 18 | .transition(AnyTransition.opacity.animation(.easeIn)) 19 | } else { 20 | List { 21 | ForEach(listViewModel.items) { item in 22 | ListRowView(item: item) 23 | .onTapGesture { 24 | withAnimation(.linear) { 25 | listViewModel.updateItem(item: item) 26 | } 27 | } 28 | } 29 | .onDelete(perform: listViewModel.deleteItem) 30 | .onMove(perform: listViewModel.moveItem) 31 | } 32 | .listStyle(PlainListStyle()) 33 | } 34 | } 35 | .navigationTitle("Todo List 📝") 36 | .navigationBarItems( 37 | leading: EditButton(), 38 | trailing: 39 | NavigationLink("Add", destination: AddView()) 40 | ) 41 | } 42 | 43 | } 44 | 45 | struct ListView_Previews: PreviewProvider { 46 | static var previews: some View { 47 | NavigationView { 48 | ListView() 49 | } 50 | .environmentObject(ListViewModel()) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /TodoList/Views/NoItemsView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NoItemsView.swift 3 | // TodoList 4 | // 5 | // Created by Nick Sarno on 3/3/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct NoItemsView: View { 11 | 12 | @State var animate: Bool = false 13 | let secondaryAccentColor = Color("SecondaryAccentColor") 14 | 15 | var body: some View { 16 | ScrollView { 17 | VStack(spacing: 10) { 18 | Text("There are no items!") 19 | .font(.title) 20 | .fontWeight(.semibold) 21 | 22 | Text("Are you a productive person? I think you should click the add button and add a bunch of items to your todo list!") 23 | .padding(.bottom, 20) 24 | 25 | NavigationLink( 26 | destination: AddView(), 27 | label: { 28 | Text("Add Something 🥳") 29 | .foregroundColor(.white) 30 | .font(.headline) 31 | .frame(height: 55) 32 | .frame(maxWidth: .infinity) 33 | .background(animate ? secondaryAccentColor : Color.accentColor) 34 | .cornerRadius(10) 35 | }) 36 | .padding(.horizontal, animate ? 30 : 50) 37 | .shadow( 38 | color: animate ? secondaryAccentColor.opacity(0.7) : Color.accentColor.opacity(0.7), 39 | radius: animate ? 30 : 10, 40 | x: 0, 41 | y: animate ? 50 : 30) 42 | .scaleEffect(animate ? 1.1 : 1.0) 43 | .offset(y: animate ? -7 : 0) 44 | } 45 | .frame(maxWidth: 400) 46 | .multilineTextAlignment(.center) 47 | .padding(40) 48 | .onAppear(perform: addAnimation) 49 | } 50 | .frame(maxWidth: .infinity, maxHeight: .infinity) 51 | } 52 | 53 | func addAnimation() { 54 | guard !animate else { return } 55 | DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { 56 | withAnimation( 57 | Animation 58 | .easeInOut(duration: 2.0) 59 | .repeatForever() 60 | ) { 61 | animate.toggle() 62 | } 63 | } 64 | } 65 | 66 | } 67 | 68 | struct NoItemsView_Previews: PreviewProvider { 69 | static var previews: some View { 70 | NavigationView { 71 | NoItemsView() 72 | .navigationTitle("Title") 73 | } 74 | } 75 | } 76 | --------------------------------------------------------------------------------