├── GymOrNotGym.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcuserdata │ └── michael.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── GymOrNotGym ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ ├── Runner.imageset │ │ ├── Contents.json │ │ └── Runner-PNG-Image-File.png │ └── Sitting.imageset │ │ ├── 100-1001503_people-sitting-png-standing-people-sitting-png-silhouette.png │ │ └── Contents.json ├── Base.lproj │ └── LaunchScreen.storyboard ├── ContentView.swift ├── ContentViewModel.swift ├── CoreData │ ├── CoreDataContainer.swift │ ├── GymEntry+Extension.swift │ └── GymOrNotGym.xcdatamodeld │ │ ├── .xccurrentversion │ │ ├── GymOrNotGym v1.xcdatamodel │ │ └── contents │ │ └── GymOrNotGym.xcdatamodel │ │ └── contents ├── Extensions │ └── DateFormatter+Extension.swift ├── GymCell.swift ├── Info.plist ├── NewGymEntryView │ ├── NewGymEntryView.swift │ └── NewGymEntryViewModel.swift ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json └── SceneDelegate.swift └── README.md /GymOrNotGym.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 4C0311BD234EDB350064D4A8 /* GymEntry+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C0311BC234EDB350064D4A8 /* GymEntry+Extension.swift */; }; 11 | 4C0311BF234F08360064D4A8 /* NewGymEntryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C0311BE234F08360064D4A8 /* NewGymEntryView.swift */; }; 12 | 4C8CF5E0234F264C00BD3913 /* NewGymEntryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8CF5DF234F264C00BD3913 /* NewGymEntryViewModel.swift */; }; 13 | 4CADEA5C235418B400E3F1EA /* DateFormatter+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CADEA5B235418B400E3F1EA /* DateFormatter+Extension.swift */; }; 14 | 4CCAEC4F234ED12F00CAB714 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CCAEC4E234ED12F00CAB714 /* AppDelegate.swift */; }; 15 | 4CCAEC51234ED12F00CAB714 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CCAEC50234ED12F00CAB714 /* SceneDelegate.swift */; }; 16 | 4CCAEC54234ED12F00CAB714 /* GymOrNotGym.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 4CCAEC52234ED12F00CAB714 /* GymOrNotGym.xcdatamodeld */; }; 17 | 4CCAEC56234ED12F00CAB714 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CCAEC55234ED12F00CAB714 /* ContentView.swift */; }; 18 | 4CCAEC58234ED13000CAB714 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4CCAEC57234ED13000CAB714 /* Assets.xcassets */; }; 19 | 4CCAEC5B234ED13000CAB714 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4CCAEC5A234ED13000CAB714 /* Preview Assets.xcassets */; }; 20 | 4CCAEC5E234ED13000CAB714 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4CCAEC5C234ED13000CAB714 /* LaunchScreen.storyboard */; }; 21 | 4CCAEC68234ED14D00CAB714 /* CoreDataContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CCAEC67234ED14D00CAB714 /* CoreDataContainer.swift */; }; 22 | 4CCAEC6A234ED26200CAB714 /* GymCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CCAEC69234ED26200CAB714 /* GymCell.swift */; }; 23 | 4CCAEC6C234ED4A500CAB714 /* ContentViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CCAEC6B234ED4A500CAB714 /* ContentViewModel.swift */; }; 24 | /* End PBXBuildFile section */ 25 | 26 | /* Begin PBXFileReference section */ 27 | 4C0311BA234ED8D00064D4A8 /* GymOrNotGym v1.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "GymOrNotGym v1.xcdatamodel"; sourceTree = ""; }; 28 | 4C0311BC234EDB350064D4A8 /* GymEntry+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GymEntry+Extension.swift"; sourceTree = ""; }; 29 | 4C0311BE234F08360064D4A8 /* NewGymEntryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewGymEntryView.swift; sourceTree = ""; }; 30 | 4C8CF5DF234F264C00BD3913 /* NewGymEntryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewGymEntryViewModel.swift; sourceTree = ""; }; 31 | 4CADEA5B235418B400E3F1EA /* DateFormatter+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DateFormatter+Extension.swift"; sourceTree = ""; }; 32 | 4CCAEC4B234ED12F00CAB714 /* GymOrNotGym.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = GymOrNotGym.app; sourceTree = BUILT_PRODUCTS_DIR; }; 33 | 4CCAEC4E234ED12F00CAB714 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 34 | 4CCAEC50234ED12F00CAB714 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 35 | 4CCAEC53234ED12F00CAB714 /* GymOrNotGym.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = GymOrNotGym.xcdatamodel; sourceTree = ""; }; 36 | 4CCAEC55234ED12F00CAB714 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 37 | 4CCAEC57234ED13000CAB714 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 38 | 4CCAEC5A234ED13000CAB714 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 39 | 4CCAEC5D234ED13000CAB714 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 40 | 4CCAEC5F234ED13000CAB714 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 41 | 4CCAEC67234ED14D00CAB714 /* CoreDataContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataContainer.swift; sourceTree = ""; }; 42 | 4CCAEC69234ED26200CAB714 /* GymCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GymCell.swift; sourceTree = ""; }; 43 | 4CCAEC6B234ED4A500CAB714 /* ContentViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentViewModel.swift; sourceTree = ""; }; 44 | /* End PBXFileReference section */ 45 | 46 | /* Begin PBXFrameworksBuildPhase section */ 47 | 4CCAEC48234ED12F00CAB714 /* Frameworks */ = { 48 | isa = PBXFrameworksBuildPhase; 49 | buildActionMask = 2147483647; 50 | files = ( 51 | ); 52 | runOnlyForDeploymentPostprocessing = 0; 53 | }; 54 | /* End PBXFrameworksBuildPhase section */ 55 | 56 | /* Begin PBXGroup section */ 57 | 4C0311BB234EDB270064D4A8 /* CoreData */ = { 58 | isa = PBXGroup; 59 | children = ( 60 | 4CCAEC52234ED12F00CAB714 /* GymOrNotGym.xcdatamodeld */, 61 | 4CCAEC67234ED14D00CAB714 /* CoreDataContainer.swift */, 62 | 4C0311BC234EDB350064D4A8 /* GymEntry+Extension.swift */, 63 | ); 64 | path = CoreData; 65 | sourceTree = ""; 66 | }; 67 | 4CADEA5D235420BF00E3F1EA /* NewGymEntryView */ = { 68 | isa = PBXGroup; 69 | children = ( 70 | 4C0311BE234F08360064D4A8 /* NewGymEntryView.swift */, 71 | 4C8CF5DF234F264C00BD3913 /* NewGymEntryViewModel.swift */, 72 | ); 73 | path = NewGymEntryView; 74 | sourceTree = ""; 75 | }; 76 | 4CADEA5E2354230600E3F1EA /* Extensions */ = { 77 | isa = PBXGroup; 78 | children = ( 79 | 4CADEA5B235418B400E3F1EA /* DateFormatter+Extension.swift */, 80 | ); 81 | path = Extensions; 82 | sourceTree = ""; 83 | }; 84 | 4CCAEC42234ED12F00CAB714 = { 85 | isa = PBXGroup; 86 | children = ( 87 | 4CCAEC4D234ED12F00CAB714 /* GymOrNotGym */, 88 | 4CCAEC4C234ED12F00CAB714 /* Products */, 89 | ); 90 | sourceTree = ""; 91 | }; 92 | 4CCAEC4C234ED12F00CAB714 /* Products */ = { 93 | isa = PBXGroup; 94 | children = ( 95 | 4CCAEC4B234ED12F00CAB714 /* GymOrNotGym.app */, 96 | ); 97 | name = Products; 98 | sourceTree = ""; 99 | }; 100 | 4CCAEC4D234ED12F00CAB714 /* GymOrNotGym */ = { 101 | isa = PBXGroup; 102 | children = ( 103 | 4CCAEC4E234ED12F00CAB714 /* AppDelegate.swift */, 104 | 4CCAEC50234ED12F00CAB714 /* SceneDelegate.swift */, 105 | 4CCAEC55234ED12F00CAB714 /* ContentView.swift */, 106 | 4CCAEC6B234ED4A500CAB714 /* ContentViewModel.swift */, 107 | 4CCAEC69234ED26200CAB714 /* GymCell.swift */, 108 | 4CADEA5D235420BF00E3F1EA /* NewGymEntryView */, 109 | 4CADEA5E2354230600E3F1EA /* Extensions */, 110 | 4C0311BB234EDB270064D4A8 /* CoreData */, 111 | 4CCAEC57234ED13000CAB714 /* Assets.xcassets */, 112 | 4CCAEC5C234ED13000CAB714 /* LaunchScreen.storyboard */, 113 | 4CCAEC5F234ED13000CAB714 /* Info.plist */, 114 | 4CCAEC59234ED13000CAB714 /* Preview Content */, 115 | ); 116 | path = GymOrNotGym; 117 | sourceTree = ""; 118 | }; 119 | 4CCAEC59234ED13000CAB714 /* Preview Content */ = { 120 | isa = PBXGroup; 121 | children = ( 122 | 4CCAEC5A234ED13000CAB714 /* Preview Assets.xcassets */, 123 | ); 124 | path = "Preview Content"; 125 | sourceTree = ""; 126 | }; 127 | /* End PBXGroup section */ 128 | 129 | /* Begin PBXNativeTarget section */ 130 | 4CCAEC4A234ED12F00CAB714 /* GymOrNotGym */ = { 131 | isa = PBXNativeTarget; 132 | buildConfigurationList = 4CCAEC62234ED13000CAB714 /* Build configuration list for PBXNativeTarget "GymOrNotGym" */; 133 | buildPhases = ( 134 | 4CCAEC47234ED12F00CAB714 /* Sources */, 135 | 4CCAEC48234ED12F00CAB714 /* Frameworks */, 136 | 4CCAEC49234ED12F00CAB714 /* Resources */, 137 | ); 138 | buildRules = ( 139 | ); 140 | dependencies = ( 141 | ); 142 | name = GymOrNotGym; 143 | productName = GymOrNotGym; 144 | productReference = 4CCAEC4B234ED12F00CAB714 /* GymOrNotGym.app */; 145 | productType = "com.apple.product-type.application"; 146 | }; 147 | /* End PBXNativeTarget section */ 148 | 149 | /* Begin PBXProject section */ 150 | 4CCAEC43234ED12F00CAB714 /* Project object */ = { 151 | isa = PBXProject; 152 | attributes = { 153 | LastSwiftUpdateCheck = 1100; 154 | LastUpgradeCheck = 1100; 155 | ORGANIZATIONNAME = Michael; 156 | TargetAttributes = { 157 | 4CCAEC4A234ED12F00CAB714 = { 158 | CreatedOnToolsVersion = 11.0; 159 | }; 160 | }; 161 | }; 162 | buildConfigurationList = 4CCAEC46234ED12F00CAB714 /* Build configuration list for PBXProject "GymOrNotGym" */; 163 | compatibilityVersion = "Xcode 9.3"; 164 | developmentRegion = en; 165 | hasScannedForEncodings = 0; 166 | knownRegions = ( 167 | en, 168 | Base, 169 | ); 170 | mainGroup = 4CCAEC42234ED12F00CAB714; 171 | productRefGroup = 4CCAEC4C234ED12F00CAB714 /* Products */; 172 | projectDirPath = ""; 173 | projectRoot = ""; 174 | targets = ( 175 | 4CCAEC4A234ED12F00CAB714 /* GymOrNotGym */, 176 | ); 177 | }; 178 | /* End PBXProject section */ 179 | 180 | /* Begin PBXResourcesBuildPhase section */ 181 | 4CCAEC49234ED12F00CAB714 /* Resources */ = { 182 | isa = PBXResourcesBuildPhase; 183 | buildActionMask = 2147483647; 184 | files = ( 185 | 4CCAEC5E234ED13000CAB714 /* LaunchScreen.storyboard in Resources */, 186 | 4CCAEC5B234ED13000CAB714 /* Preview Assets.xcassets in Resources */, 187 | 4CCAEC58234ED13000CAB714 /* Assets.xcassets in Resources */, 188 | ); 189 | runOnlyForDeploymentPostprocessing = 0; 190 | }; 191 | /* End PBXResourcesBuildPhase section */ 192 | 193 | /* Begin PBXSourcesBuildPhase section */ 194 | 4CCAEC47234ED12F00CAB714 /* Sources */ = { 195 | isa = PBXSourcesBuildPhase; 196 | buildActionMask = 2147483647; 197 | files = ( 198 | 4C8CF5E0234F264C00BD3913 /* NewGymEntryViewModel.swift in Sources */, 199 | 4CCAEC54234ED12F00CAB714 /* GymOrNotGym.xcdatamodeld in Sources */, 200 | 4CCAEC4F234ED12F00CAB714 /* AppDelegate.swift in Sources */, 201 | 4C0311BD234EDB350064D4A8 /* GymEntry+Extension.swift in Sources */, 202 | 4CCAEC56234ED12F00CAB714 /* ContentView.swift in Sources */, 203 | 4CCAEC6C234ED4A500CAB714 /* ContentViewModel.swift in Sources */, 204 | 4CCAEC68234ED14D00CAB714 /* CoreDataContainer.swift in Sources */, 205 | 4CCAEC51234ED12F00CAB714 /* SceneDelegate.swift in Sources */, 206 | 4C0311BF234F08360064D4A8 /* NewGymEntryView.swift in Sources */, 207 | 4CADEA5C235418B400E3F1EA /* DateFormatter+Extension.swift in Sources */, 208 | 4CCAEC6A234ED26200CAB714 /* GymCell.swift in Sources */, 209 | ); 210 | runOnlyForDeploymentPostprocessing = 0; 211 | }; 212 | /* End PBXSourcesBuildPhase section */ 213 | 214 | /* Begin PBXVariantGroup section */ 215 | 4CCAEC5C234ED13000CAB714 /* LaunchScreen.storyboard */ = { 216 | isa = PBXVariantGroup; 217 | children = ( 218 | 4CCAEC5D234ED13000CAB714 /* Base */, 219 | ); 220 | name = LaunchScreen.storyboard; 221 | sourceTree = ""; 222 | }; 223 | /* End PBXVariantGroup section */ 224 | 225 | /* Begin XCBuildConfiguration section */ 226 | 4CCAEC60234ED13000CAB714 /* Debug */ = { 227 | isa = XCBuildConfiguration; 228 | buildSettings = { 229 | ALWAYS_SEARCH_USER_PATHS = NO; 230 | CLANG_ANALYZER_NONNULL = YES; 231 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 232 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 233 | CLANG_CXX_LIBRARY = "libc++"; 234 | CLANG_ENABLE_MODULES = YES; 235 | CLANG_ENABLE_OBJC_ARC = YES; 236 | CLANG_ENABLE_OBJC_WEAK = YES; 237 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 238 | CLANG_WARN_BOOL_CONVERSION = YES; 239 | CLANG_WARN_COMMA = YES; 240 | CLANG_WARN_CONSTANT_CONVERSION = YES; 241 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 242 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 243 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 244 | CLANG_WARN_EMPTY_BODY = YES; 245 | CLANG_WARN_ENUM_CONVERSION = YES; 246 | CLANG_WARN_INFINITE_RECURSION = YES; 247 | CLANG_WARN_INT_CONVERSION = YES; 248 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 249 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 250 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 251 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 252 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 253 | CLANG_WARN_STRICT_PROTOTYPES = YES; 254 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 255 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 256 | CLANG_WARN_UNREACHABLE_CODE = YES; 257 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 258 | COPY_PHASE_STRIP = NO; 259 | DEBUG_INFORMATION_FORMAT = dwarf; 260 | ENABLE_STRICT_OBJC_MSGSEND = YES; 261 | ENABLE_TESTABILITY = YES; 262 | GCC_C_LANGUAGE_STANDARD = gnu11; 263 | GCC_DYNAMIC_NO_PIC = NO; 264 | GCC_NO_COMMON_BLOCKS = YES; 265 | GCC_OPTIMIZATION_LEVEL = 0; 266 | GCC_PREPROCESSOR_DEFINITIONS = ( 267 | "DEBUG=1", 268 | "$(inherited)", 269 | ); 270 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 271 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 272 | GCC_WARN_UNDECLARED_SELECTOR = YES; 273 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 274 | GCC_WARN_UNUSED_FUNCTION = YES; 275 | GCC_WARN_UNUSED_VARIABLE = YES; 276 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 277 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 278 | MTL_FAST_MATH = YES; 279 | ONLY_ACTIVE_ARCH = YES; 280 | SDKROOT = iphoneos; 281 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 282 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 283 | }; 284 | name = Debug; 285 | }; 286 | 4CCAEC61234ED13000CAB714 /* Release */ = { 287 | isa = XCBuildConfiguration; 288 | buildSettings = { 289 | ALWAYS_SEARCH_USER_PATHS = NO; 290 | CLANG_ANALYZER_NONNULL = YES; 291 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 292 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 293 | CLANG_CXX_LIBRARY = "libc++"; 294 | CLANG_ENABLE_MODULES = YES; 295 | CLANG_ENABLE_OBJC_ARC = YES; 296 | CLANG_ENABLE_OBJC_WEAK = YES; 297 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 298 | CLANG_WARN_BOOL_CONVERSION = YES; 299 | CLANG_WARN_COMMA = YES; 300 | CLANG_WARN_CONSTANT_CONVERSION = YES; 301 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 302 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 303 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 304 | CLANG_WARN_EMPTY_BODY = YES; 305 | CLANG_WARN_ENUM_CONVERSION = YES; 306 | CLANG_WARN_INFINITE_RECURSION = YES; 307 | CLANG_WARN_INT_CONVERSION = YES; 308 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 309 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 310 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 311 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 312 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 313 | CLANG_WARN_STRICT_PROTOTYPES = YES; 314 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 315 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 316 | CLANG_WARN_UNREACHABLE_CODE = YES; 317 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 318 | COPY_PHASE_STRIP = NO; 319 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 320 | ENABLE_NS_ASSERTIONS = NO; 321 | ENABLE_STRICT_OBJC_MSGSEND = YES; 322 | GCC_C_LANGUAGE_STANDARD = gnu11; 323 | GCC_NO_COMMON_BLOCKS = YES; 324 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 325 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 326 | GCC_WARN_UNDECLARED_SELECTOR = YES; 327 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 328 | GCC_WARN_UNUSED_FUNCTION = YES; 329 | GCC_WARN_UNUSED_VARIABLE = YES; 330 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 331 | MTL_ENABLE_DEBUG_INFO = NO; 332 | MTL_FAST_MATH = YES; 333 | SDKROOT = iphoneos; 334 | SWIFT_COMPILATION_MODE = wholemodule; 335 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 336 | VALIDATE_PRODUCT = YES; 337 | }; 338 | name = Release; 339 | }; 340 | 4CCAEC63234ED13000CAB714 /* Debug */ = { 341 | isa = XCBuildConfiguration; 342 | buildSettings = { 343 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 344 | CODE_SIGN_STYLE = Automatic; 345 | DEVELOPMENT_ASSET_PATHS = "\"GymOrNotGym/Preview Content\""; 346 | ENABLE_PREVIEWS = YES; 347 | INFOPLIST_FILE = GymOrNotGym/Info.plist; 348 | LD_RUNPATH_SEARCH_PATHS = ( 349 | "$(inherited)", 350 | "@executable_path/Frameworks", 351 | ); 352 | PRODUCT_BUNDLE_IDENTIFIER = com.michael.GymOrNotGym; 353 | PRODUCT_NAME = "$(TARGET_NAME)"; 354 | SWIFT_VERSION = 5.0; 355 | TARGETED_DEVICE_FAMILY = "1,2"; 356 | }; 357 | name = Debug; 358 | }; 359 | 4CCAEC64234ED13000CAB714 /* Release */ = { 360 | isa = XCBuildConfiguration; 361 | buildSettings = { 362 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 363 | CODE_SIGN_STYLE = Automatic; 364 | DEVELOPMENT_ASSET_PATHS = "\"GymOrNotGym/Preview Content\""; 365 | ENABLE_PREVIEWS = YES; 366 | INFOPLIST_FILE = GymOrNotGym/Info.plist; 367 | LD_RUNPATH_SEARCH_PATHS = ( 368 | "$(inherited)", 369 | "@executable_path/Frameworks", 370 | ); 371 | PRODUCT_BUNDLE_IDENTIFIER = com.michael.GymOrNotGym; 372 | PRODUCT_NAME = "$(TARGET_NAME)"; 373 | SWIFT_VERSION = 5.0; 374 | TARGETED_DEVICE_FAMILY = "1,2"; 375 | }; 376 | name = Release; 377 | }; 378 | /* End XCBuildConfiguration section */ 379 | 380 | /* Begin XCConfigurationList section */ 381 | 4CCAEC46234ED12F00CAB714 /* Build configuration list for PBXProject "GymOrNotGym" */ = { 382 | isa = XCConfigurationList; 383 | buildConfigurations = ( 384 | 4CCAEC60234ED13000CAB714 /* Debug */, 385 | 4CCAEC61234ED13000CAB714 /* Release */, 386 | ); 387 | defaultConfigurationIsVisible = 0; 388 | defaultConfigurationName = Release; 389 | }; 390 | 4CCAEC62234ED13000CAB714 /* Build configuration list for PBXNativeTarget "GymOrNotGym" */ = { 391 | isa = XCConfigurationList; 392 | buildConfigurations = ( 393 | 4CCAEC63234ED13000CAB714 /* Debug */, 394 | 4CCAEC64234ED13000CAB714 /* Release */, 395 | ); 396 | defaultConfigurationIsVisible = 0; 397 | defaultConfigurationName = Release; 398 | }; 399 | /* End XCConfigurationList section */ 400 | 401 | /* Begin XCVersionGroup section */ 402 | 4CCAEC52234ED12F00CAB714 /* GymOrNotGym.xcdatamodeld */ = { 403 | isa = XCVersionGroup; 404 | children = ( 405 | 4C0311BA234ED8D00064D4A8 /* GymOrNotGym v1.xcdatamodel */, 406 | 4CCAEC53234ED12F00CAB714 /* GymOrNotGym.xcdatamodel */, 407 | ); 408 | currentVersion = 4CCAEC53234ED12F00CAB714 /* GymOrNotGym.xcdatamodel */; 409 | path = GymOrNotGym.xcdatamodeld; 410 | sourceTree = ""; 411 | versionGroupType = wrapper.xcdatamodel; 412 | }; 413 | /* End XCVersionGroup section */ 414 | }; 415 | rootObject = 4CCAEC43234ED12F00CAB714 /* Project object */; 416 | } 417 | -------------------------------------------------------------------------------- /GymOrNotGym.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /GymOrNotGym.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /GymOrNotGym.xcodeproj/xcuserdata/michael.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | GymOrNotGym.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 4CCAEC4A234ED12F00CAB714 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /GymOrNotGym/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // GymOrNotGym 4 | // 5 | // Created by Michael on 10/10/19. 6 | // Copyright © 2019 Michael. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 15 | // Override point for customization after application launch. 16 | return true 17 | } 18 | 19 | // MARK: UISceneSession Lifecycle 20 | 21 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 22 | // Called when a new scene session is being created. 23 | // Use this method to select a configuration to create the new scene with. 24 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 25 | } 26 | 27 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 28 | // Called when the user discards a scene session. 29 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 30 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 31 | } 32 | 33 | } 34 | 35 | -------------------------------------------------------------------------------- /GymOrNotGym/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /GymOrNotGym/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /GymOrNotGym/Assets.xcassets/Runner.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "Runner-PNG-Image-File.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /GymOrNotGym/Assets.xcassets/Runner.imageset/Runner-PNG-Image-File.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikechan1234/SwiftUI-Using-Core-Data/9b0575579a83ee4e6d468679bd6127ac738ae42e/GymOrNotGym/Assets.xcassets/Runner.imageset/Runner-PNG-Image-File.png -------------------------------------------------------------------------------- /GymOrNotGym/Assets.xcassets/Sitting.imageset/100-1001503_people-sitting-png-standing-people-sitting-png-silhouette.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikechan1234/SwiftUI-Using-Core-Data/9b0575579a83ee4e6d468679bd6127ac738ae42e/GymOrNotGym/Assets.xcassets/Sitting.imageset/100-1001503_people-sitting-png-standing-people-sitting-png-silhouette.png -------------------------------------------------------------------------------- /GymOrNotGym/Assets.xcassets/Sitting.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "100-1001503_people-sitting-png-standing-people-sitting-png-silhouette.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /GymOrNotGym/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /GymOrNotGym/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // GymOrNotGym 4 | // 5 | // Created by Michael on 10/10/19. 6 | // Copyright © 2019 Michael. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct ContentView: View { 12 | 13 | @State var addEntryPresented: Bool = false 14 | @FetchRequest(fetchRequest: GymEntry.makeSortedFetchRequest(), animation: .default) var gymEntry: FetchedResults 15 | // var contentViewModel: ContentViewModel 16 | 17 | var body: some View { 18 | 19 | VStack { 20 | 21 | List(gymEntry, id: \.self) { (gymEntry) -> GymCell in 22 | GymCell(gymEntry: gymEntry) 23 | } 24 | 25 | // List(contentViewModel.subject.value, id: \.self) { (gymEntry) -> GymCell in 26 | // GymCell(gymEntry: gymEntry) 27 | // } 28 | 29 | 30 | }.navigationBarTitle("Gym Entries").navigationBarItems(trailing: 31 | 32 | Button(action: { 33 | 34 | self.addEntryPresented = true 35 | 36 | }) { 37 | 38 | Text("New") 39 | 40 | } 41 | 42 | ).sheet(isPresented: self.$addEntryPresented) { 43 | 44 | NavigationView { 45 | 46 | NewGymEntryView() 47 | 48 | } 49 | 50 | } 51 | 52 | } 53 | 54 | } 55 | 56 | struct ContentView_Previews: PreviewProvider { 57 | 58 | static var previews: some View { 59 | 60 | NavigationView { 61 | // ContentView(contentViewModel: ContentViewModel(CoreDataContainer.main.persistentContainer.viewContext)) 62 | ContentView().environment(\.managedObjectContext, CoreDataContainer.main.persistentContainer.viewContext) 63 | } 64 | 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /GymOrNotGym/ContentViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentViewModel.swift 3 | // GymOrNotGym 4 | // 5 | // Created by Michael on 10/10/19. 6 | // Copyright © 2019 Michael. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreData 11 | import Combine 12 | 13 | class ContentViewModel: NSObject, ObservableObject { 14 | 15 | var fetchedResultsController: NSFetchedResultsController 16 | var entriesSubject: CurrentValueSubject<[GymEntry], Never> 17 | 18 | init(_ managedObjectContext: NSManagedObjectContext) { 19 | 20 | fetchedResultsController = NSFetchedResultsController(fetchRequest: GymEntry.makeSortedFetchRequest(), managedObjectContext: managedObjectContext, sectionNameKeyPath: nil, cacheName: nil) 21 | entriesSubject = CurrentValueSubject<[GymEntry], Never>([]) 22 | 23 | super.init() 24 | 25 | fetchedResultsController.delegate = self 26 | 27 | do { 28 | 29 | try fetchedResultsController.performFetch() 30 | 31 | entriesSubject.send(fetchedResultsController.fetchedObjects ?? []) 32 | 33 | } catch { 34 | 35 | print(error) 36 | 37 | } 38 | 39 | } 40 | 41 | } 42 | 43 | //MARK: NSFetchedResultsControllerDelegate 44 | extension ContentViewModel: NSFetchedResultsControllerDelegate { 45 | 46 | func controllerDidChangeContent(_ controller: NSFetchedResultsController) { 47 | 48 | guard let entries = controller.fetchedObjects as? [GymEntry] else { 49 | return 50 | } 51 | 52 | entriesSubject.send(entries) 53 | 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /GymOrNotGym/CoreData/CoreDataContainer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CoreDataContainer.swift 3 | // GymOrNotGym 4 | // 5 | // Created by Michael on 10/10/19. 6 | // Copyright © 2019 Michael. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreData 11 | 12 | class CoreDataContainer: ObservableObject { 13 | 14 | static var main = CoreDataContainer() 15 | 16 | private init() { 17 | 18 | persistentContainer.viewContext.automaticallyMergesChangesFromParent = true 19 | 20 | } 21 | 22 | var persistentContainer: NSPersistentContainer = { 23 | /* 24 | The persistent container for the application. This implementation 25 | creates and returns a container, having loaded the store for the 26 | application to it. This property is optional since there are legitimate 27 | error conditions that could cause the creation of the store to fail. 28 | */ 29 | let container = NSPersistentContainer(name: "GymOrNotGym") 30 | container.loadPersistentStores(completionHandler: { (storeDescription, error) in 31 | if let error = error as NSError? { 32 | // Replace this implementation with code to handle the error appropriately. 33 | // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. 34 | 35 | /* 36 | Typical reasons for an error here include: 37 | * The parent directory does not exist, cannot be created, or disallows writing. 38 | * The persistent store is not accessible, due to permissions or data protection when the device is locked. 39 | * The device is out of space. 40 | * The store could not be migrated to the current model version. 41 | Check the error message to determine what the actual problem was. 42 | */ 43 | fatalError("Unresolved error \(error), \(error.userInfo)") 44 | } 45 | }) 46 | return container 47 | }() 48 | 49 | func saveContext () { 50 | let context = persistentContainer.viewContext 51 | if context.hasChanges { 52 | do { 53 | try context.save() 54 | } catch { 55 | // Replace this implementation with code to handle the error appropriately. 56 | // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. 57 | let nserror = error as NSError 58 | fatalError("Unresolved error \(nserror), \(nserror.userInfo)") 59 | } 60 | } 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /GymOrNotGym/CoreData/GymEntry+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GymEntry+Extension.swift 3 | // GymOrNotGym 4 | // 5 | // Created by Michael on 10/10/19. 6 | // Copyright © 2019 Michael. All rights reserved. 7 | // 8 | 9 | import CoreData 10 | 11 | extension GymEntry { 12 | 13 | var formattedDate: String { 14 | 15 | guard let date = date else { 16 | return "" 17 | } 18 | 19 | return DateFormatter.shortDate.string(from: date) 20 | 21 | } 22 | 23 | } 24 | 25 | //MARK: FetchRequest factory function 26 | 27 | extension GymEntry { 28 | 29 | public class func makeSortedFetchRequest() -> NSFetchRequest { 30 | 31 | let fetchRequest = NSFetchRequest(entityName: String(describing: GymEntry.self)) 32 | let sortDescriptor = NSSortDescriptor(keyPath: \GymEntry.date, ascending: false) 33 | 34 | fetchRequest.sortDescriptors = [sortDescriptor] 35 | 36 | return fetchRequest 37 | 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /GymOrNotGym/CoreData/GymOrNotGym.xcdatamodeld/.xccurrentversion: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | _XCCurrentVersionName 6 | GymOrNotGym.xcdatamodel 7 | 8 | 9 | -------------------------------------------------------------------------------- /GymOrNotGym/CoreData/GymOrNotGym.xcdatamodeld/GymOrNotGym v1.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /GymOrNotGym/CoreData/GymOrNotGym.xcdatamodeld/GymOrNotGym.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /GymOrNotGym/Extensions/DateFormatter+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DateFormatter+Extension.swift 3 | // GymOrNotGym 4 | // 5 | // Created by Michael on 14/10/19. 6 | // Copyright © 2019 Michael. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension DateFormatter { 12 | 13 | static var shortDate: DateFormatter { 14 | 15 | let formatter = DateFormatter() 16 | 17 | formatter.dateFormat = "dd/MM/yyyy" 18 | 19 | return formatter 20 | 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /GymOrNotGym/GymCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GymCell.swift 3 | // GymOrNotGym 4 | // 5 | // Created by Michael on 10/10/19. 6 | // Copyright © 2019 Michael. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import CoreData 11 | 12 | struct GymCell: View { 13 | 14 | let gymEntry: GymEntry 15 | 16 | var body: some View { 17 | 18 | VStack(alignment: .leading, spacing: 5) { 19 | 20 | Text(gymEntry.attended ? "You went to the gym" : "You didn't go to the gym") 21 | Text(gymEntry.formattedDate).font(.caption) 22 | 23 | }.padding(5) 24 | 25 | } 26 | 27 | } 28 | 29 | struct GymCell_Previews: PreviewProvider { 30 | 31 | static let entry: GymEntry = { 32 | 33 | let entry = GymEntry(context: CoreDataContainer.main.persistentContainer.viewContext) 34 | 35 | entry.attended = true 36 | entry.date = Date() 37 | 38 | return entry 39 | 40 | }() 41 | 42 | static var previews: some View { 43 | 44 | GymCell(gymEntry: entry) 45 | 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /GymOrNotGym/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 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | 37 | 38 | 39 | 40 | UILaunchStoryboardName 41 | LaunchScreen 42 | UIRequiredDeviceCapabilities 43 | 44 | armv7 45 | 46 | UISupportedInterfaceOrientations 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationLandscapeLeft 50 | UIInterfaceOrientationLandscapeRight 51 | 52 | UISupportedInterfaceOrientations~ipad 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationPortraitUpsideDown 56 | UIInterfaceOrientationLandscapeLeft 57 | UIInterfaceOrientationLandscapeRight 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /GymOrNotGym/NewGymEntryView/NewGymEntryView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NewGymEntryView.swift 3 | // GymOrNotGym 4 | // 5 | // Created by Michael on 10/10/19. 6 | // Copyright © 2019 Michael. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct NewGymEntryView: View { 12 | 13 | @Environment(\.presentationMode) var presentationMode 14 | @ObservedObject var viewModel = NewGymEntryViewModel() 15 | 16 | var body: some View { 17 | 18 | VStack { 19 | 20 | Spacer() 21 | 22 | Image($viewModel.attended.wrappedValue ? "Runner" : "Sitting").resizable(capInsets: EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0), resizingMode: .stretch).scaledToFit().frame(height: 300, alignment: .center) 23 | 24 | Spacer() 25 | 26 | HStack(alignment: .center, spacing: 30) { 27 | 28 | Button(action: { 29 | 30 | self.viewModel.attended = true 31 | 32 | }) { 33 | 34 | Text("I Did Go") 35 | 36 | } 37 | Button(action: { 38 | 39 | self.viewModel.attended = false 40 | 41 | }) { 42 | 43 | Text("I Didn't Go") 44 | 45 | } 46 | 47 | }.frame(height: 100, alignment: .center) 48 | 49 | }.navigationBarTitle("New Entry").navigationBarItems(trailing: 50 | 51 | Button(action: { 52 | 53 | self.viewModel.addEntry { 54 | 55 | self.presentationMode.wrappedValue.dismiss() 56 | 57 | } 58 | 59 | }, label: { 60 | 61 | Text("Save") 62 | 63 | }) 64 | 65 | ) 66 | 67 | } 68 | 69 | } 70 | 71 | struct NewGymEntry_Previews: PreviewProvider { 72 | 73 | static var previews: some View { 74 | 75 | NavigationView { 76 | 77 | NewGymEntryView() 78 | 79 | } 80 | 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /GymOrNotGym/NewGymEntryView/NewGymEntryViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NewGymEntryViewModel.swift 3 | // GymOrNotGym 4 | // 5 | // Created by Michael on 10/10/19. 6 | // Copyright © 2019 Michael. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class NewGymEntryViewModel: ObservableObject { 12 | 13 | private unowned var coreData: CoreDataContainer 14 | 15 | @Published var attended: Bool = false 16 | 17 | init(coreData: CoreDataContainer = .main) { 18 | 19 | self.coreData = coreData 20 | 21 | } 22 | 23 | } 24 | 25 | extension NewGymEntryViewModel { 26 | 27 | func addEntry(completion: (()->())? = nil) { 28 | 29 | let backgroundContext = coreData.persistentContainer.newBackgroundContext() 30 | 31 | backgroundContext.performAndWait { 32 | 33 | let entry = GymEntry(context: backgroundContext) 34 | 35 | entry.attended = self.attended 36 | entry.date = Date() 37 | 38 | if backgroundContext.hasChanges { 39 | 40 | do { 41 | 42 | try backgroundContext.save() 43 | 44 | DispatchQueue.main.async { 45 | 46 | completion?() 47 | 48 | } 49 | 50 | } catch let error { 51 | 52 | print(error.localizedDescription) 53 | 54 | } 55 | 56 | } 57 | 58 | } 59 | 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /GymOrNotGym/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /GymOrNotGym/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // GymOrNotGym 4 | // 5 | // Created by Michael on 10/10/19. 6 | // Copyright © 2019 Michael. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SwiftUI 11 | 12 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 13 | 14 | var window: UIWindow? 15 | var coreData = CoreDataContainer.main 16 | 17 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 18 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 19 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 20 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 21 | 22 | // Get the managed object context from the shared persistent container. 23 | let context = coreData.persistentContainer.viewContext 24 | 25 | // Create the SwiftUI view and set the context as the value for the managedObjectContext environment keyPath. 26 | // Add `@Environment(\.managedObjectContext)` in the views that will need the context. 27 | let contentView = ContentView().environment(\.managedObjectContext, context) 28 | // let contentView = ContentView(contentViewModel: ContentViewModel(context)) 29 | 30 | let navigationView = NavigationView { 31 | contentView 32 | } 33 | 34 | // Use a UIHostingController as window root view controller. 35 | if let windowScene = scene as? UIWindowScene { 36 | let window = UIWindow(windowScene: windowScene) 37 | window.rootViewController = UIHostingController(rootView: navigationView) 38 | self.window = window 39 | window.makeKeyAndVisible() 40 | } 41 | } 42 | 43 | func sceneDidEnterBackground(_ scene: UIScene) { 44 | // Called as the scene transitions from the foreground to the background. 45 | // Use this method to save data, release shared resources, and store enough scene-specific state information 46 | // to restore the scene back to its current state. 47 | 48 | // Save changes in the application's managed object context when the application transitions to the background. 49 | coreData.saveContext() 50 | } 51 | 52 | } 53 | 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SwiftUI-Using-Core-Data 2 | Basic concept of using SwiftUI with Core Data via FetchRequest property wrapper or NSFetchedResultsController 3 | 4 | - Developed in Xcode 11 5 | - This is a simple SwiftUI application that shows a list of entries to display whether the user went to the gym or not 6 | - The app displays 2 methods of fetching the entries from Core Data and displaying it on List 7 | - FetchRequest property wrapper which requires managedObjectContext as an environment variable set within the view 8 | - NSFetchedResultsController with a CurrentValueSubject 9 | - Presents a modal view via .sheet view modifier and using presentationModel @Environment variable to dismiss the view 10 | --------------------------------------------------------------------------------