├── BlogIdeaList.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcuserdata │ └── andrewcbancroft.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── BlogIdeaList ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── BlogIdeaEditorViewController.swift ├── BlogIdeaList.entitlements ├── Core Data │ ├── BlogIdea.swift │ └── BlogIdeaList.xcdatamodeld │ │ ├── .xccurrentversion │ │ └── BlogIdeaList.xcdatamodel │ │ └── contents ├── Info.plist ├── MainViewController.swift └── SceneDelegate.swift └── README.md /BlogIdeaList.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | CE95EDC922AA6AC40020BCAD /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE95EDC822AA6AC40020BCAD /* AppDelegate.swift */; }; 11 | CE95EDCB22AA6AC40020BCAD /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE95EDCA22AA6AC40020BCAD /* SceneDelegate.swift */; }; 12 | CE95EDCD22AA6AC40020BCAD /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE95EDCC22AA6AC40020BCAD /* MainViewController.swift */; }; 13 | CE95EDD022AA6AC40020BCAD /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CE95EDCE22AA6AC40020BCAD /* Main.storyboard */; }; 14 | CE95EDD322AA6AC40020BCAD /* BlogIdeaList.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = CE95EDD122AA6AC40020BCAD /* BlogIdeaList.xcdatamodeld */; }; 15 | CE95EDD522AA6AC40020BCAD /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CE95EDD422AA6AC40020BCAD /* Assets.xcassets */; }; 16 | CE95EDD822AA6AC40020BCAD /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CE95EDD622AA6AC40020BCAD /* LaunchScreen.storyboard */; }; 17 | CE95EDE222AA6AF00020BCAD /* CloudKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE95EDE122AA6AF00020BCAD /* CloudKit.framework */; }; 18 | CE95EDE422AA714E0020BCAD /* BlogIdeaEditorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE95EDE322AA714E0020BCAD /* BlogIdeaEditorViewController.swift */; }; 19 | CE95EDE822AA73180020BCAD /* BlogIdea.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE95EDE622AA73180020BCAD /* BlogIdea.swift */; }; 20 | CEE5073F22AFC80700F0F221 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = CEE5073E22AFC80700F0F221 /* README.md */; }; 21 | /* End PBXBuildFile section */ 22 | 23 | /* Begin PBXFileReference section */ 24 | CE95EDC522AA6AC40020BCAD /* BlogIdeaList.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BlogIdeaList.app; sourceTree = BUILT_PRODUCTS_DIR; }; 25 | CE95EDC822AA6AC40020BCAD /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 26 | CE95EDCA22AA6AC40020BCAD /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 27 | CE95EDCC22AA6AC40020BCAD /* MainViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = ""; }; 28 | CE95EDCF22AA6AC40020BCAD /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 29 | CE95EDD222AA6AC40020BCAD /* BlogIdeaList.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = BlogIdeaList.xcdatamodel; sourceTree = ""; }; 30 | CE95EDD422AA6AC40020BCAD /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 31 | CE95EDD722AA6AC40020BCAD /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 32 | CE95EDD922AA6AC40020BCAD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 33 | CE95EDDF22AA6AEB0020BCAD /* BlogIdeaList.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = BlogIdeaList.entitlements; sourceTree = ""; }; 34 | CE95EDE122AA6AF00020BCAD /* CloudKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CloudKit.framework; path = System/Library/Frameworks/CloudKit.framework; sourceTree = SDKROOT; }; 35 | CE95EDE322AA714E0020BCAD /* BlogIdeaEditorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlogIdeaEditorViewController.swift; sourceTree = ""; }; 36 | CE95EDE622AA73180020BCAD /* BlogIdea.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlogIdea.swift; sourceTree = ""; }; 37 | CEE5073E22AFC80700F0F221 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 38 | /* End PBXFileReference section */ 39 | 40 | /* Begin PBXFrameworksBuildPhase section */ 41 | CE95EDC222AA6AC40020BCAD /* Frameworks */ = { 42 | isa = PBXFrameworksBuildPhase; 43 | buildActionMask = 2147483647; 44 | files = ( 45 | CE95EDE222AA6AF00020BCAD /* CloudKit.framework in Frameworks */, 46 | ); 47 | runOnlyForDeploymentPostprocessing = 0; 48 | }; 49 | /* End PBXFrameworksBuildPhase section */ 50 | 51 | /* Begin PBXGroup section */ 52 | CE5CE6F922AE593800D4EFA2 /* Core Data */ = { 53 | isa = PBXGroup; 54 | children = ( 55 | CE95EDD122AA6AC40020BCAD /* BlogIdeaList.xcdatamodeld */, 56 | CE95EDE622AA73180020BCAD /* BlogIdea.swift */, 57 | ); 58 | path = "Core Data"; 59 | sourceTree = ""; 60 | }; 61 | CE95EDBC22AA6AC40020BCAD = { 62 | isa = PBXGroup; 63 | children = ( 64 | CEE5073E22AFC80700F0F221 /* README.md */, 65 | CE95EDC722AA6AC40020BCAD /* BlogIdeaList */, 66 | CE95EDC622AA6AC40020BCAD /* Products */, 67 | CE95EDE022AA6AF00020BCAD /* Frameworks */, 68 | ); 69 | sourceTree = ""; 70 | }; 71 | CE95EDC622AA6AC40020BCAD /* Products */ = { 72 | isa = PBXGroup; 73 | children = ( 74 | CE95EDC522AA6AC40020BCAD /* BlogIdeaList.app */, 75 | ); 76 | name = Products; 77 | sourceTree = ""; 78 | }; 79 | CE95EDC722AA6AC40020BCAD /* BlogIdeaList */ = { 80 | isa = PBXGroup; 81 | children = ( 82 | CE5CE6F922AE593800D4EFA2 /* Core Data */, 83 | CE95EDDF22AA6AEB0020BCAD /* BlogIdeaList.entitlements */, 84 | CE95EDC822AA6AC40020BCAD /* AppDelegate.swift */, 85 | CE95EDCA22AA6AC40020BCAD /* SceneDelegate.swift */, 86 | CE95EDCC22AA6AC40020BCAD /* MainViewController.swift */, 87 | CE95EDE322AA714E0020BCAD /* BlogIdeaEditorViewController.swift */, 88 | CE95EDCE22AA6AC40020BCAD /* Main.storyboard */, 89 | CE95EDD422AA6AC40020BCAD /* Assets.xcassets */, 90 | CE95EDD622AA6AC40020BCAD /* LaunchScreen.storyboard */, 91 | CE95EDD922AA6AC40020BCAD /* Info.plist */, 92 | ); 93 | path = BlogIdeaList; 94 | sourceTree = ""; 95 | }; 96 | CE95EDE022AA6AF00020BCAD /* Frameworks */ = { 97 | isa = PBXGroup; 98 | children = ( 99 | CE95EDE122AA6AF00020BCAD /* CloudKit.framework */, 100 | ); 101 | name = Frameworks; 102 | sourceTree = ""; 103 | }; 104 | /* End PBXGroup section */ 105 | 106 | /* Begin PBXNativeTarget section */ 107 | CE95EDC422AA6AC40020BCAD /* BlogIdeaList */ = { 108 | isa = PBXNativeTarget; 109 | buildConfigurationList = CE95EDDC22AA6AC40020BCAD /* Build configuration list for PBXNativeTarget "BlogIdeaList" */; 110 | buildPhases = ( 111 | CE95EDC122AA6AC40020BCAD /* Sources */, 112 | CE95EDC222AA6AC40020BCAD /* Frameworks */, 113 | CE95EDC322AA6AC40020BCAD /* Resources */, 114 | ); 115 | buildRules = ( 116 | ); 117 | dependencies = ( 118 | ); 119 | name = BlogIdeaList; 120 | productName = BlogIdeaList; 121 | productReference = CE95EDC522AA6AC40020BCAD /* BlogIdeaList.app */; 122 | productType = "com.apple.product-type.application"; 123 | }; 124 | /* End PBXNativeTarget section */ 125 | 126 | /* Begin PBXProject section */ 127 | CE95EDBD22AA6AC40020BCAD /* Project object */ = { 128 | isa = PBXProject; 129 | attributes = { 130 | LastSwiftUpdateCheck = 1100; 131 | LastUpgradeCheck = 1100; 132 | ORGANIZATIONNAME = "Andrew Bancroft"; 133 | TargetAttributes = { 134 | CE95EDC422AA6AC40020BCAD = { 135 | CreatedOnToolsVersion = 11.0; 136 | }; 137 | }; 138 | }; 139 | buildConfigurationList = CE95EDC022AA6AC40020BCAD /* Build configuration list for PBXProject "BlogIdeaList" */; 140 | compatibilityVersion = "Xcode 9.3"; 141 | developmentRegion = en; 142 | hasScannedForEncodings = 0; 143 | knownRegions = ( 144 | en, 145 | Base, 146 | ); 147 | mainGroup = CE95EDBC22AA6AC40020BCAD; 148 | productRefGroup = CE95EDC622AA6AC40020BCAD /* Products */; 149 | projectDirPath = ""; 150 | projectRoot = ""; 151 | targets = ( 152 | CE95EDC422AA6AC40020BCAD /* BlogIdeaList */, 153 | ); 154 | }; 155 | /* End PBXProject section */ 156 | 157 | /* Begin PBXResourcesBuildPhase section */ 158 | CE95EDC322AA6AC40020BCAD /* Resources */ = { 159 | isa = PBXResourcesBuildPhase; 160 | buildActionMask = 2147483647; 161 | files = ( 162 | CE95EDD822AA6AC40020BCAD /* LaunchScreen.storyboard in Resources */, 163 | CE95EDD522AA6AC40020BCAD /* Assets.xcassets in Resources */, 164 | CEE5073F22AFC80700F0F221 /* README.md in Resources */, 165 | CE95EDD022AA6AC40020BCAD /* Main.storyboard in Resources */, 166 | ); 167 | runOnlyForDeploymentPostprocessing = 0; 168 | }; 169 | /* End PBXResourcesBuildPhase section */ 170 | 171 | /* Begin PBXSourcesBuildPhase section */ 172 | CE95EDC122AA6AC40020BCAD /* Sources */ = { 173 | isa = PBXSourcesBuildPhase; 174 | buildActionMask = 2147483647; 175 | files = ( 176 | CE95EDD322AA6AC40020BCAD /* BlogIdeaList.xcdatamodeld in Sources */, 177 | CE95EDCD22AA6AC40020BCAD /* MainViewController.swift in Sources */, 178 | CE95EDE822AA73180020BCAD /* BlogIdea.swift in Sources */, 179 | CE95EDE422AA714E0020BCAD /* BlogIdeaEditorViewController.swift in Sources */, 180 | CE95EDC922AA6AC40020BCAD /* AppDelegate.swift in Sources */, 181 | CE95EDCB22AA6AC40020BCAD /* SceneDelegate.swift in Sources */, 182 | ); 183 | runOnlyForDeploymentPostprocessing = 0; 184 | }; 185 | /* End PBXSourcesBuildPhase section */ 186 | 187 | /* Begin PBXVariantGroup section */ 188 | CE95EDCE22AA6AC40020BCAD /* Main.storyboard */ = { 189 | isa = PBXVariantGroup; 190 | children = ( 191 | CE95EDCF22AA6AC40020BCAD /* Base */, 192 | ); 193 | name = Main.storyboard; 194 | sourceTree = ""; 195 | }; 196 | CE95EDD622AA6AC40020BCAD /* LaunchScreen.storyboard */ = { 197 | isa = PBXVariantGroup; 198 | children = ( 199 | CE95EDD722AA6AC40020BCAD /* Base */, 200 | ); 201 | name = LaunchScreen.storyboard; 202 | sourceTree = ""; 203 | }; 204 | /* End PBXVariantGroup section */ 205 | 206 | /* Begin XCBuildConfiguration section */ 207 | CE95EDDA22AA6AC40020BCAD /* Debug */ = { 208 | isa = XCBuildConfiguration; 209 | buildSettings = { 210 | ALWAYS_SEARCH_USER_PATHS = NO; 211 | CLANG_ANALYZER_NONNULL = YES; 212 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 213 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 214 | CLANG_CXX_LIBRARY = "libc++"; 215 | CLANG_ENABLE_MODULES = YES; 216 | CLANG_ENABLE_OBJC_ARC = YES; 217 | CLANG_ENABLE_OBJC_WEAK = YES; 218 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 219 | CLANG_WARN_BOOL_CONVERSION = YES; 220 | CLANG_WARN_COMMA = YES; 221 | CLANG_WARN_CONSTANT_CONVERSION = YES; 222 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 223 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 224 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 225 | CLANG_WARN_EMPTY_BODY = YES; 226 | CLANG_WARN_ENUM_CONVERSION = YES; 227 | CLANG_WARN_INFINITE_RECURSION = YES; 228 | CLANG_WARN_INT_CONVERSION = YES; 229 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 230 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 231 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 232 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 233 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 234 | CLANG_WARN_STRICT_PROTOTYPES = YES; 235 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 236 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 237 | CLANG_WARN_UNREACHABLE_CODE = YES; 238 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 239 | COPY_PHASE_STRIP = NO; 240 | DEBUG_INFORMATION_FORMAT = dwarf; 241 | ENABLE_STRICT_OBJC_MSGSEND = YES; 242 | ENABLE_TESTABILITY = YES; 243 | GCC_C_LANGUAGE_STANDARD = gnu11; 244 | GCC_DYNAMIC_NO_PIC = NO; 245 | GCC_NO_COMMON_BLOCKS = YES; 246 | GCC_OPTIMIZATION_LEVEL = 0; 247 | GCC_PREPROCESSOR_DEFINITIONS = ( 248 | "DEBUG=1", 249 | "$(inherited)", 250 | ); 251 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 252 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 253 | GCC_WARN_UNDECLARED_SELECTOR = YES; 254 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 255 | GCC_WARN_UNUSED_FUNCTION = YES; 256 | GCC_WARN_UNUSED_VARIABLE = YES; 257 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 258 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 259 | MTL_FAST_MATH = YES; 260 | ONLY_ACTIVE_ARCH = YES; 261 | SDKROOT = iphoneos; 262 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 263 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 264 | }; 265 | name = Debug; 266 | }; 267 | CE95EDDB22AA6AC40020BCAD /* Release */ = { 268 | isa = XCBuildConfiguration; 269 | buildSettings = { 270 | ALWAYS_SEARCH_USER_PATHS = NO; 271 | CLANG_ANALYZER_NONNULL = YES; 272 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 273 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 274 | CLANG_CXX_LIBRARY = "libc++"; 275 | CLANG_ENABLE_MODULES = YES; 276 | CLANG_ENABLE_OBJC_ARC = YES; 277 | CLANG_ENABLE_OBJC_WEAK = YES; 278 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 279 | CLANG_WARN_BOOL_CONVERSION = YES; 280 | CLANG_WARN_COMMA = YES; 281 | CLANG_WARN_CONSTANT_CONVERSION = YES; 282 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 283 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 284 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 285 | CLANG_WARN_EMPTY_BODY = YES; 286 | CLANG_WARN_ENUM_CONVERSION = YES; 287 | CLANG_WARN_INFINITE_RECURSION = YES; 288 | CLANG_WARN_INT_CONVERSION = YES; 289 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 290 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 291 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 292 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 293 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 294 | CLANG_WARN_STRICT_PROTOTYPES = YES; 295 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 296 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 297 | CLANG_WARN_UNREACHABLE_CODE = YES; 298 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 299 | COPY_PHASE_STRIP = NO; 300 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 301 | ENABLE_NS_ASSERTIONS = NO; 302 | ENABLE_STRICT_OBJC_MSGSEND = YES; 303 | GCC_C_LANGUAGE_STANDARD = gnu11; 304 | GCC_NO_COMMON_BLOCKS = YES; 305 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 306 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 307 | GCC_WARN_UNDECLARED_SELECTOR = YES; 308 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 309 | GCC_WARN_UNUSED_FUNCTION = YES; 310 | GCC_WARN_UNUSED_VARIABLE = YES; 311 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 312 | MTL_ENABLE_DEBUG_INFO = NO; 313 | MTL_FAST_MATH = YES; 314 | SDKROOT = iphoneos; 315 | SWIFT_COMPILATION_MODE = wholemodule; 316 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 317 | VALIDATE_PRODUCT = YES; 318 | }; 319 | name = Release; 320 | }; 321 | CE95EDDD22AA6AC40020BCAD /* Debug */ = { 322 | isa = XCBuildConfiguration; 323 | buildSettings = { 324 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 325 | CODE_SIGN_ENTITLEMENTS = BlogIdeaList/BlogIdeaList.entitlements; 326 | CODE_SIGN_STYLE = Automatic; 327 | DEVELOPMENT_TEAM = 3534V3UJNS; 328 | INFOPLIST_FILE = BlogIdeaList/Info.plist; 329 | LD_RUNPATH_SEARCH_PATHS = ( 330 | "$(inherited)", 331 | "@executable_path/Frameworks", 332 | ); 333 | PRODUCT_BUNDLE_IDENTIFIER = com.andrewcbancroft.BlogIdeaList; 334 | PRODUCT_NAME = "$(TARGET_NAME)"; 335 | SWIFT_VERSION = 5.0; 336 | TARGETED_DEVICE_FAMILY = "1,2"; 337 | }; 338 | name = Debug; 339 | }; 340 | CE95EDDE22AA6AC40020BCAD /* Release */ = { 341 | isa = XCBuildConfiguration; 342 | buildSettings = { 343 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 344 | CODE_SIGN_ENTITLEMENTS = BlogIdeaList/BlogIdeaList.entitlements; 345 | CODE_SIGN_STYLE = Automatic; 346 | DEVELOPMENT_TEAM = 3534V3UJNS; 347 | INFOPLIST_FILE = BlogIdeaList/Info.plist; 348 | LD_RUNPATH_SEARCH_PATHS = ( 349 | "$(inherited)", 350 | "@executable_path/Frameworks", 351 | ); 352 | PRODUCT_BUNDLE_IDENTIFIER = com.andrewcbancroft.BlogIdeaList; 353 | PRODUCT_NAME = "$(TARGET_NAME)"; 354 | SWIFT_VERSION = 5.0; 355 | TARGETED_DEVICE_FAMILY = "1,2"; 356 | }; 357 | name = Release; 358 | }; 359 | /* End XCBuildConfiguration section */ 360 | 361 | /* Begin XCConfigurationList section */ 362 | CE95EDC022AA6AC40020BCAD /* Build configuration list for PBXProject "BlogIdeaList" */ = { 363 | isa = XCConfigurationList; 364 | buildConfigurations = ( 365 | CE95EDDA22AA6AC40020BCAD /* Debug */, 366 | CE95EDDB22AA6AC40020BCAD /* Release */, 367 | ); 368 | defaultConfigurationIsVisible = 0; 369 | defaultConfigurationName = Release; 370 | }; 371 | CE95EDDC22AA6AC40020BCAD /* Build configuration list for PBXNativeTarget "BlogIdeaList" */ = { 372 | isa = XCConfigurationList; 373 | buildConfigurations = ( 374 | CE95EDDD22AA6AC40020BCAD /* Debug */, 375 | CE95EDDE22AA6AC40020BCAD /* Release */, 376 | ); 377 | defaultConfigurationIsVisible = 0; 378 | defaultConfigurationName = Release; 379 | }; 380 | /* End XCConfigurationList section */ 381 | 382 | /* Begin XCVersionGroup section */ 383 | CE95EDD122AA6AC40020BCAD /* BlogIdeaList.xcdatamodeld */ = { 384 | isa = XCVersionGroup; 385 | children = ( 386 | CE95EDD222AA6AC40020BCAD /* BlogIdeaList.xcdatamodel */, 387 | ); 388 | currentVersion = CE95EDD222AA6AC40020BCAD /* BlogIdeaList.xcdatamodel */; 389 | path = BlogIdeaList.xcdatamodeld; 390 | sourceTree = ""; 391 | versionGroupType = wrapper.xcdatamodel; 392 | }; 393 | /* End XCVersionGroup section */ 394 | }; 395 | rootObject = CE95EDBD22AA6AC40020BCAD /* Project object */; 396 | } 397 | -------------------------------------------------------------------------------- /BlogIdeaList.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /BlogIdeaList.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /BlogIdeaList.xcodeproj/xcuserdata/andrewcbancroft.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | BlogIdeaList.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | CE95EDC422AA6AC40020BCAD 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /BlogIdeaList/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // BlogIdeaList 4 | // 5 | // Created by Andrew Bancroft on 6/7/19. 6 | // Copyright © 2019 Andrew Bancroft. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CoreData 11 | 12 | @UIApplicationMain 13 | class AppDelegate: UIResponder, UIApplicationDelegate { 14 | 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillTerminate(_ application: UIApplication) { 23 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 24 | // Saves changes in the application's managed object context before the application terminates. 25 | self.saveContext() 26 | } 27 | 28 | // MARK: UISceneSession Lifecycle 29 | 30 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 31 | // Called when a new scene session is being created. 32 | // Use this method to select a configuration to create the new scene with. 33 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 34 | } 35 | 36 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 37 | // Called when the user discards a scene session. 38 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 39 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 40 | } 41 | 42 | // MARK: - Core Data stack 43 | 44 | lazy var persistentContainer: NSPersistentCloudKitContainer = { 45 | /* 46 | The persistent container for the application. This implementation 47 | creates and returns a container, having loaded the store for the 48 | application to it. This property is optional since there are legitimate 49 | error conditions that could cause the creation of the store to fail. 50 | */ 51 | let container = NSPersistentCloudKitContainer(name: "BlogIdeaList") 52 | container.loadPersistentStores(completionHandler: { (storeDescription, error) in 53 | if let error = error as NSError? { 54 | // Replace this implementation with code to handle the error appropriately. 55 | // 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. 56 | 57 | /* 58 | Typical reasons for an error here include: 59 | * The parent directory does not exist, cannot be created, or disallows writing. 60 | * The persistent store is not accessible, due to permissions or data protection when the device is locked. 61 | * The device is out of space. 62 | * The store could not be migrated to the current model version. 63 | Check the error message to determine what the actual problem was. 64 | */ 65 | fatalError("Unresolved error \(error), \(error.userInfo)") 66 | } 67 | }) 68 | return container 69 | }() 70 | 71 | // MARK: - Core Data Saving support 72 | 73 | func saveContext () { 74 | let context = persistentContainer.viewContext 75 | if context.hasChanges { 76 | do { 77 | try context.save() 78 | } catch { 79 | // Replace this implementation with code to handle the error appropriately. 80 | // 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. 81 | let nserror = error as NSError 82 | fatalError("Unresolved error \(nserror), \(nserror.userInfo)") 83 | } 84 | } 85 | } 86 | 87 | } 88 | 89 | -------------------------------------------------------------------------------- /BlogIdeaList/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 | } -------------------------------------------------------------------------------- /BlogIdeaList/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /BlogIdeaList/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 | -------------------------------------------------------------------------------- /BlogIdeaList/Base.lproj/Main.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 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 40 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 107 | 108 | 109 | 110 | 111 | 112 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | -------------------------------------------------------------------------------- /BlogIdeaList/BlogIdeaEditorViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BlogIdeaEditorViewController.swift 3 | // BlogIdeaList 4 | // 5 | // Created by Andrew Bancroft on 6/7/19. 6 | // Copyright © 2019 Andrew Bancroft. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CoreData 11 | 12 | class BlogIdeaEditorViewController: UIViewController { 13 | 14 | var managedObjectContext: NSManagedObjectContext! 15 | var blogIdea: BlogIdea! 16 | 17 | @IBOutlet weak var titleTextField: UITextField! 18 | @IBOutlet weak var descriptionTextField: UITextField! 19 | 20 | // MARK: - View Controller Lifecycle 21 | override func viewDidLoad() { 22 | super.viewDidLoad() 23 | 24 | // Do any additional setup after loading the view. 25 | 26 | self.setUIValues() 27 | } 28 | 29 | // MARK: - Set UI 30 | func setUIValues() { 31 | guard let blogIdea = self.blogIdea else { return } 32 | 33 | self.titleTextField.text = blogIdea.ideaTitle 34 | self.descriptionTextField.text = blogIdea.ideaDescription 35 | } 36 | 37 | // MARK: - Save 38 | // In a storyboard-based application, you will often want to do a little preparation before navigation 39 | @IBAction func saveButtonTapped(_ sender: Any) { 40 | if self.blogIdea == nil { 41 | self.blogIdea = (NSEntityDescription.insertNewObject(forEntityName: BlogIdea.entityName, 42 | into: self.managedObjectContext) as! BlogIdea) 43 | } 44 | 45 | self.blogIdea.ideaTitle = self.titleTextField.text 46 | self.blogIdea.ideaDescription = self.descriptionTextField.text 47 | 48 | do { 49 | try self.managedObjectContext.save() 50 | _ = self.navigationController?.popViewController(animated: true) 51 | } catch { 52 | let alert = UIAlertController(title: "Trouble Saving", 53 | message: "Something went wrong when trying to save the Blog Idea. Please try again...", 54 | preferredStyle: .alert) 55 | let okAction = UIAlertAction(title: "OK", 56 | style: .default, 57 | handler: {(action: UIAlertAction) -> Void in 58 | self.managedObjectContext.rollback() 59 | self.blogIdea = NSEntityDescription.insertNewObject(forEntityName: BlogIdea.entityName, into: self.managedObjectContext) as? BlogIdea 60 | 61 | }) 62 | alert.addAction(okAction) 63 | self.present(alert, animated: true, completion: nil) 64 | } 65 | } 66 | 67 | 68 | // MARK: - Navigation 69 | 70 | // In a storyboard-based application, you will often want to do a little preparation before navigation 71 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 72 | // Get the new view controller using segue.destination. 73 | // Pass the selected object to the new view controller. 74 | self.managedObjectContext.rollback() 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /BlogIdeaList/BlogIdeaList.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | aps-environment 6 | development 7 | com.apple.developer.icloud-container-identifiers 8 | 9 | iCloud.$(CFBundleIdentifier) 10 | 11 | com.apple.developer.icloud-services 12 | 13 | CloudKit 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /BlogIdeaList/Core Data/BlogIdea.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BlogIdea+CoreDataProperties.swift 3 | // BlogIdeaList 4 | // 5 | // Created by Andrew Bancroft on 6/7/19. 6 | // Copyright © 2019 Andrew Bancroft. All rights reserved. 7 | // 8 | // 9 | 10 | import Foundation 11 | import CoreData 12 | 13 | 14 | public class BlogIdea: NSManagedObject { 15 | 16 | @NSManaged public var ideaTitle: String? 17 | @NSManaged public var ideaDescription: String? 18 | 19 | static var entityName: String { return "BlogIdea" } 20 | } 21 | -------------------------------------------------------------------------------- /BlogIdeaList/Core Data/BlogIdeaList.xcdatamodeld/.xccurrentversion: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | _XCCurrentVersionName 6 | BlogIdeaList.xcdatamodel 7 | 8 | 9 | -------------------------------------------------------------------------------- /BlogIdeaList/Core Data/BlogIdeaList.xcdatamodeld/BlogIdeaList.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /BlogIdeaList/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 | UILaunchStoryboardName 33 | LaunchScreen 34 | UISceneConfigurationName 35 | Default Configuration 36 | UISceneDelegateClassName 37 | $(PRODUCT_MODULE_NAME).SceneDelegate 38 | UISceneStoryboardFile 39 | Main 40 | 41 | 42 | 43 | 44 | UIBackgroundModes 45 | 46 | remote-notification 47 | 48 | UILaunchStoryboardName 49 | LaunchScreen 50 | UIMainStoryboardFile 51 | Main 52 | UIRequiredDeviceCapabilities 53 | 54 | armv7 55 | 56 | UISupportedInterfaceOrientations 57 | 58 | UIInterfaceOrientationPortrait 59 | UIInterfaceOrientationLandscapeLeft 60 | UIInterfaceOrientationLandscapeRight 61 | 62 | UISupportedInterfaceOrientations~ipad 63 | 64 | UIInterfaceOrientationPortrait 65 | UIInterfaceOrientationPortraitUpsideDown 66 | UIInterfaceOrientationLandscapeLeft 67 | UIInterfaceOrientationLandscapeRight 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /BlogIdeaList/MainViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // BlogIdeaList 4 | // 5 | // Created by Andrew Bancroft on 6/7/19. 6 | // Copyright © 2019 Andrew Bancroft. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CoreData 11 | 12 | class MainViewController: UIViewController, NSFetchedResultsControllerDelegate, UITableViewDataSource, UITableViewDelegate { 13 | 14 | var managedObjectContext: NSManagedObjectContext! 15 | 16 | @IBOutlet weak var tableView: UITableView! 17 | var fetchedResultsController: NSFetchedResultsController! 18 | 19 | // MARK: - View Controller Lifecycle 20 | override func viewDidLoad() { 21 | super.viewDidLoad() 22 | // Do any additional setup after loading the view. 23 | } 24 | 25 | override func viewWillAppear(_ animated: Bool) { 26 | configureFetchedResultsController() 27 | 28 | do { 29 | try fetchedResultsController.performFetch() 30 | } catch { 31 | print("An error occurred") 32 | 33 | } 34 | } 35 | 36 | // MARK: Fetched Results Controller Configuration 37 | func configureFetchedResultsController() { 38 | let blogIdeasFetchRequest = NSFetchRequest(entityName: "BlogIdea") 39 | let primarySortDescriptor = NSSortDescriptor(key: "ideaTitle", ascending: true) 40 | blogIdeasFetchRequest.sortDescriptors = [primarySortDescriptor] 41 | 42 | self.fetchedResultsController = NSFetchedResultsController( 43 | fetchRequest: blogIdeasFetchRequest, 44 | managedObjectContext: self.managedObjectContext, 45 | sectionNameKeyPath: nil, 46 | cacheName: nil) 47 | 48 | self.fetchedResultsController.delegate = self 49 | 50 | } 51 | 52 | 53 | // MARK: TableView Data Source 54 | public func numberOfSections(in tableView: UITableView) -> Int { 55 | if let sections = fetchedResultsController.sections { 56 | return sections.count 57 | } 58 | 59 | return 0 60 | } 61 | 62 | public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 63 | if let sections = fetchedResultsController.sections { 64 | let currentSection = sections[section] 65 | return currentSection.numberOfObjects 66 | } 67 | 68 | return 0 69 | } 70 | 71 | public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 72 | let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) 73 | let blogIdea = fetchedResultsController.object(at: indexPath) 74 | 75 | cell.textLabel?.text = blogIdea.ideaTitle 76 | cell.detailTextLabel?.text = blogIdea.ideaDescription 77 | 78 | return cell 79 | } 80 | 81 | // MARK: TableView Delegate 82 | public func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { 83 | if editingStyle == UITableViewCell.EditingStyle.delete { 84 | let blogIdea = fetchedResultsController.object(at: indexPath) 85 | confirmDeleteForBlogIdea(blogIdea) 86 | } 87 | } 88 | 89 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 90 | tableView.deselectRow(at: indexPath, animated: true) 91 | } 92 | 93 | // MARK: Delete Confirmation and Handling 94 | var blogIdeaToDelete: BlogIdea? 95 | 96 | func confirmDeleteForBlogIdea(_ blogIdea: BlogIdea) { 97 | 98 | self.blogIdeaToDelete = blogIdea 99 | 100 | let alertController = UIAlertController(title: "Delete Blog Idea", 101 | message: "Are you sure you want to delete this Blog Idea?", 102 | preferredStyle: .actionSheet) 103 | 104 | let deleteAction = UIAlertAction(title: "Delete", style: .destructive) { 105 | (_) -> Void in 106 | 107 | self.managedObjectContext.delete(self.blogIdeaToDelete!) 108 | 109 | do { 110 | try self.managedObjectContext.save() 111 | } catch { 112 | self.managedObjectContext.rollback() 113 | print("Something went wrong: \(error)") 114 | } 115 | } 116 | 117 | let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) 118 | 119 | alertController.addAction(deleteAction) 120 | alertController.addAction(cancelAction) 121 | 122 | self.present(alertController, animated: true, completion: nil) 123 | } 124 | 125 | // MARK: NSFetchedResultsController Delegate methods 126 | func controllerWillChangeContent(_ controller: NSFetchedResultsController) { 127 | self.tableView.beginUpdates() 128 | } 129 | 130 | func controllerDidChangeContent(_ controller: NSFetchedResultsController) { 131 | self.tableView.endUpdates() 132 | } 133 | 134 | func controller(_ controller: NSFetchedResultsController, 135 | didChange anObject: Any, 136 | at indexPath: IndexPath?, 137 | for type: NSFetchedResultsChangeType, 138 | newIndexPath: IndexPath?) { 139 | switch type { 140 | case .insert: 141 | if let insertIndexPath = newIndexPath { 142 | self.tableView.insertRows(at: [insertIndexPath], with: .fade) 143 | } 144 | case .delete: 145 | if let deleteIndexPath = indexPath { 146 | self.tableView.deleteRows(at: [deleteIndexPath], with: .fade) 147 | } 148 | case .update: 149 | if let updateIndexPath = indexPath { 150 | let cell = self.tableView.cellForRow(at: updateIndexPath) 151 | let updatedBlogIdea = self.fetchedResultsController.object(at: updateIndexPath) 152 | 153 | cell?.textLabel?.text = updatedBlogIdea.ideaTitle 154 | cell?.detailTextLabel?.text = updatedBlogIdea.ideaDescription 155 | } 156 | case .move: 157 | if let deleteIndexPath = indexPath { 158 | self.tableView.deleteRows(at: [deleteIndexPath], with: .fade) 159 | } 160 | 161 | if let insertIndexPath = newIndexPath { 162 | self.tableView.insertRows(at: [insertIndexPath], with: .fade) 163 | } 164 | @unknown default: 165 | fatalError() 166 | } 167 | } 168 | 169 | func controller(_ controller: NSFetchedResultsController, 170 | sectionIndexTitleForSectionName sectionName: String) -> String? { 171 | return sectionName 172 | } 173 | 174 | func controller(_ controller: NSFetchedResultsController, 175 | didChange sectionInfo: NSFetchedResultsSectionInfo, 176 | atSectionIndex sectionIndex: Int, 177 | for type: NSFetchedResultsChangeType) { 178 | let sectionIndexSet = NSIndexSet(index: sectionIndex) as IndexSet 179 | 180 | switch type { 181 | case .insert: 182 | self.tableView.insertSections(sectionIndexSet, with: .fade) 183 | case .delete: 184 | self.tableView.deleteSections(sectionIndexSet, with: .fade) 185 | default: 186 | break 187 | } 188 | } 189 | 190 | // MARK: - Navigation 191 | 192 | // In a storyboard-based application, you will often want to do a little preparation before navigation 193 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 194 | // Get the new view controller using segue.destination. 195 | // Pass the selected object to the new view controller. 196 | guard let editorVC = segue.destination as? BlogIdeaEditorViewController else { return } 197 | editorVC.managedObjectContext = self.managedObjectContext 198 | 199 | if let selectedIndexPath = self.tableView.indexPathForSelectedRow { 200 | let selectedBlogIdea = self.fetchedResultsController.object(at: selectedIndexPath) 201 | editorVC.blogIdea = selectedBlogIdea 202 | } 203 | } 204 | } 205 | 206 | -------------------------------------------------------------------------------- /BlogIdeaList/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // BlogIdeaList 4 | // 5 | // Created by Andrew Bancroft on 6/7/19. 6 | // Copyright © 2019 Andrew Bancroft. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CoreData 11 | 12 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 13 | 14 | var window: UIWindow? 15 | 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 | guard let _ = (scene as? UIWindowScene) else { return } 22 | 23 | let navigationController = self.window?.rootViewController as! UINavigationController 24 | let mainVC = navigationController.viewControllers[0] as! MainViewController 25 | 26 | let viewContext = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext 27 | viewContext.automaticallyMergesChangesFromParent = true 28 | 29 | mainVC.managedObjectContext = viewContext 30 | } 31 | 32 | func sceneDidDisconnect(_ scene: UIScene) { 33 | // Called as the scene is being released by the system. 34 | // This occurs shortly after the scene enters the background, or when its session is discarded. 35 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 36 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). 37 | } 38 | 39 | func sceneDidBecomeActive(_ scene: UIScene) { 40 | // Called when the scene has moved from an inactive state to an active state. 41 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 42 | } 43 | 44 | func sceneWillResignActive(_ scene: UIScene) { 45 | // Called when the scene will move from an active state to an inactive state. 46 | // This may occur due to temporary interruptions (ex. an incoming phone call). 47 | } 48 | 49 | func sceneWillEnterForeground(_ scene: UIScene) { 50 | // Called as the scene transitions from the background to the foreground. 51 | // Use this method to undo the changes made on entering the background. 52 | } 53 | 54 | func sceneDidEnterBackground(_ scene: UIScene) { 55 | // Called as the scene transitions from the foreground to the background. 56 | // Use this method to save data, release shared resources, and store enough scene-specific state information 57 | // to restore the scene back to its current state. 58 | 59 | // Save changes in the application's managed object context when the application transitions to the background. 60 | (UIApplication.shared.delegate as? AppDelegate)?.saveContext() 61 | } 62 | 63 | 64 | } 65 | 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Getting Started With NSPersistentCloudKitContainer 2 | 3 | ## Overview 4 | 5 | What is it like to sync data from Core Cata to iCloud using Apple's new `NSPersistentCloudKitContainer`? Here I provide an example project for saving and syncing a single Core Data Entity with CloudKit using this new class. 6 | 7 | This sample code project is associated [Getting Started With NSPersistentCloudKitContainer](https://www.andrewcbancroft.com/blog/ios-development/data-persistence/getting-started-with-nspersistentcloudkitcontainer/). 8 | 9 | ## Configure the Sample Code Project 10 | 11 | Before you run the sample code project in Xcode: 12 | 13 | 1. Set your bundle identifier. In the project settings, under General, in the Identity section, set the Bundle Identifier to **your** reverse domain name followed by the project name. 14 | 2. Set your development team. In the project settings, under Signing & Capabilities, select your development team from the dropdown list. 15 | --------------------------------------------------------------------------------