├── .gitignore ├── CoreDataSync.xcodeproj └── project.pbxproj ├── CoreDataSync ├── App │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ ├── Config.swift │ ├── CoreDataSync.entitlements │ ├── CoreDataSyncApp.swift │ └── Info.plist ├── Core Data │ ├── CoreDataSync.xcdatamodeld │ │ ├── .xccurrentversion │ │ └── CoreDataSync.xcdatamodel │ │ │ └── contents │ └── PersistenceController.swift └── Views │ ├── AddContactView.swift │ ├── ContentView.swift │ └── ImagePicker.swift ├── CoreDataSyncTests ├── CoreDataSyncTests.swift └── Info.plist ├── LICENSE.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # macOS 2 | .DS_Store 3 | 4 | # Xcode 5 | **/*.xcodeproj/project.xcworkspace/* 6 | .build/ 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | *.xcuserdatad 24 | *.dSYM* 25 | -------------------------------------------------------------------------------- /CoreDataSync.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | F4AE96E625F2D2EE00019801 /* CoreDataSyncApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4AE96E525F2D2EE00019801 /* CoreDataSyncApp.swift */; }; 11 | F4AE96E825F2D2EE00019801 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4AE96E725F2D2EE00019801 /* ContentView.swift */; }; 12 | F4AE96EA25F2D2EF00019801 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F4AE96E925F2D2EF00019801 /* Assets.xcassets */; }; 13 | F4AE96EF25F2D2EF00019801 /* PersistenceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4AE96EE25F2D2EF00019801 /* PersistenceController.swift */; }; 14 | F4AE96F225F2D2EF00019801 /* CoreDataSync.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = F4AE96F025F2D2EF00019801 /* CoreDataSync.xcdatamodeld */; }; 15 | F4AE96FD25F2D2EF00019801 /* CoreDataSyncTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4AE96FC25F2D2EF00019801 /* CoreDataSyncTests.swift */; }; 16 | F4AE971825F2D58A00019801 /* CloudKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F4AE971725F2D58A00019801 /* CloudKit.framework */; }; 17 | F4AE971F25F684B000019801 /* ImagePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4AE971E25F684B000019801 /* ImagePicker.swift */; }; 18 | F4AE972125F69E7F00019801 /* AddContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4AE972025F69E7F00019801 /* AddContactView.swift */; }; 19 | F4C904F926138E7E00252717 /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4C904F826138E7E00252717 /* Config.swift */; }; 20 | /* End PBXBuildFile section */ 21 | 22 | /* Begin PBXContainerItemProxy section */ 23 | F4AE96F925F2D2EF00019801 /* PBXContainerItemProxy */ = { 24 | isa = PBXContainerItemProxy; 25 | containerPortal = F4AE96DA25F2D2EE00019801 /* Project object */; 26 | proxyType = 1; 27 | remoteGlobalIDString = F4AE96E125F2D2EE00019801; 28 | remoteInfo = CoreDataSync; 29 | }; 30 | /* End PBXContainerItemProxy section */ 31 | 32 | /* Begin PBXFileReference section */ 33 | F4AE96E225F2D2EE00019801 /* CoreDataSync.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CoreDataSync.app; sourceTree = BUILT_PRODUCTS_DIR; }; 34 | F4AE96E525F2D2EE00019801 /* CoreDataSyncApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataSyncApp.swift; sourceTree = ""; }; 35 | F4AE96E725F2D2EE00019801 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 36 | F4AE96E925F2D2EF00019801 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 37 | F4AE96EE25F2D2EF00019801 /* PersistenceController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistenceController.swift; sourceTree = ""; }; 38 | F4AE96F125F2D2EF00019801 /* CoreDataSync.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = CoreDataSync.xcdatamodel; sourceTree = ""; }; 39 | F4AE96F325F2D2EF00019801 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 40 | F4AE96F825F2D2EF00019801 /* CoreDataSyncTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CoreDataSyncTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 41 | F4AE96FC25F2D2EF00019801 /* CoreDataSyncTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataSyncTests.swift; sourceTree = ""; }; 42 | F4AE96FE25F2D2EF00019801 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 43 | F4AE971525F2D58000019801 /* CoreDataSync.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = CoreDataSync.entitlements; sourceTree = ""; }; 44 | F4AE971725F2D58A00019801 /* CloudKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CloudKit.framework; path = System/Library/Frameworks/CloudKit.framework; sourceTree = SDKROOT; }; 45 | F4AE971E25F684B000019801 /* ImagePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePicker.swift; sourceTree = ""; }; 46 | F4AE972025F69E7F00019801 /* AddContactView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddContactView.swift; sourceTree = ""; }; 47 | F4C904F826138E7E00252717 /* Config.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Config.swift; sourceTree = ""; }; 48 | /* End PBXFileReference section */ 49 | 50 | /* Begin PBXFrameworksBuildPhase section */ 51 | F4AE96DF25F2D2EE00019801 /* Frameworks */ = { 52 | isa = PBXFrameworksBuildPhase; 53 | buildActionMask = 2147483647; 54 | files = ( 55 | F4AE971825F2D58A00019801 /* CloudKit.framework in Frameworks */, 56 | ); 57 | runOnlyForDeploymentPostprocessing = 0; 58 | }; 59 | F4AE96F525F2D2EF00019801 /* Frameworks */ = { 60 | isa = PBXFrameworksBuildPhase; 61 | buildActionMask = 2147483647; 62 | files = ( 63 | ); 64 | runOnlyForDeploymentPostprocessing = 0; 65 | }; 66 | /* End PBXFrameworksBuildPhase section */ 67 | 68 | /* Begin PBXGroup section */ 69 | F4AE96D925F2D2EE00019801 = { 70 | isa = PBXGroup; 71 | children = ( 72 | F4AE96E425F2D2EE00019801 /* CoreDataSync */, 73 | F4AE96FB25F2D2EF00019801 /* CoreDataSyncTests */, 74 | F4AE96E325F2D2EE00019801 /* Products */, 75 | F4AE971625F2D58A00019801 /* Frameworks */, 76 | ); 77 | sourceTree = ""; 78 | }; 79 | F4AE96E325F2D2EE00019801 /* Products */ = { 80 | isa = PBXGroup; 81 | children = ( 82 | F4AE96E225F2D2EE00019801 /* CoreDataSync.app */, 83 | F4AE96F825F2D2EF00019801 /* CoreDataSyncTests.xctest */, 84 | ); 85 | name = Products; 86 | sourceTree = ""; 87 | }; 88 | F4AE96E425F2D2EE00019801 /* CoreDataSync */ = { 89 | isa = PBXGroup; 90 | children = ( 91 | F4AE972825F6ADB000019801 /* App */, 92 | F4AE972625F6AD6100019801 /* Views */, 93 | F4AE972925F6AE1F00019801 /* Core Data */, 94 | ); 95 | path = CoreDataSync; 96 | sourceTree = ""; 97 | }; 98 | F4AE96FB25F2D2EF00019801 /* CoreDataSyncTests */ = { 99 | isa = PBXGroup; 100 | children = ( 101 | F4AE96FC25F2D2EF00019801 /* CoreDataSyncTests.swift */, 102 | F4AE96FE25F2D2EF00019801 /* Info.plist */, 103 | ); 104 | path = CoreDataSyncTests; 105 | sourceTree = ""; 106 | }; 107 | F4AE971625F2D58A00019801 /* Frameworks */ = { 108 | isa = PBXGroup; 109 | children = ( 110 | F4AE971725F2D58A00019801 /* CloudKit.framework */, 111 | ); 112 | name = Frameworks; 113 | sourceTree = ""; 114 | }; 115 | F4AE972625F6AD6100019801 /* Views */ = { 116 | isa = PBXGroup; 117 | children = ( 118 | F4AE96E725F2D2EE00019801 /* ContentView.swift */, 119 | F4AE972025F69E7F00019801 /* AddContactView.swift */, 120 | F4AE971E25F684B000019801 /* ImagePicker.swift */, 121 | ); 122 | path = Views; 123 | sourceTree = ""; 124 | }; 125 | F4AE972825F6ADB000019801 /* App */ = { 126 | isa = PBXGroup; 127 | children = ( 128 | F4C904F826138E7E00252717 /* Config.swift */, 129 | F4AE96E525F2D2EE00019801 /* CoreDataSyncApp.swift */, 130 | F4AE971525F2D58000019801 /* CoreDataSync.entitlements */, 131 | F4AE96F325F2D2EF00019801 /* Info.plist */, 132 | F4AE96E925F2D2EF00019801 /* Assets.xcassets */, 133 | ); 134 | path = App; 135 | sourceTree = ""; 136 | }; 137 | F4AE972925F6AE1F00019801 /* Core Data */ = { 138 | isa = PBXGroup; 139 | children = ( 140 | F4AE96EE25F2D2EF00019801 /* PersistenceController.swift */, 141 | F4AE96F025F2D2EF00019801 /* CoreDataSync.xcdatamodeld */, 142 | ); 143 | path = "Core Data"; 144 | sourceTree = ""; 145 | }; 146 | /* End PBXGroup section */ 147 | 148 | /* Begin PBXNativeTarget section */ 149 | F4AE96E125F2D2EE00019801 /* CoreDataSync */ = { 150 | isa = PBXNativeTarget; 151 | buildConfigurationList = F4AE970C25F2D2EF00019801 /* Build configuration list for PBXNativeTarget "CoreDataSync" */; 152 | buildPhases = ( 153 | F4AE96DE25F2D2EE00019801 /* Sources */, 154 | F4AE96DF25F2D2EE00019801 /* Frameworks */, 155 | F4AE96E025F2D2EE00019801 /* Resources */, 156 | ); 157 | buildRules = ( 158 | ); 159 | dependencies = ( 160 | ); 161 | name = CoreDataSync; 162 | productName = CoreDataSync; 163 | productReference = F4AE96E225F2D2EE00019801 /* CoreDataSync.app */; 164 | productType = "com.apple.product-type.application"; 165 | }; 166 | F4AE96F725F2D2EF00019801 /* CoreDataSyncTests */ = { 167 | isa = PBXNativeTarget; 168 | buildConfigurationList = F4AE970F25F2D2EF00019801 /* Build configuration list for PBXNativeTarget "CoreDataSyncTests" */; 169 | buildPhases = ( 170 | F4AE96F425F2D2EF00019801 /* Sources */, 171 | F4AE96F525F2D2EF00019801 /* Frameworks */, 172 | F4AE96F625F2D2EF00019801 /* Resources */, 173 | ); 174 | buildRules = ( 175 | ); 176 | dependencies = ( 177 | F4AE96FA25F2D2EF00019801 /* PBXTargetDependency */, 178 | ); 179 | name = CoreDataSyncTests; 180 | productName = CoreDataSyncTests; 181 | productReference = F4AE96F825F2D2EF00019801 /* CoreDataSyncTests.xctest */; 182 | productType = "com.apple.product-type.bundle.unit-test"; 183 | }; 184 | /* End PBXNativeTarget section */ 185 | 186 | /* Begin PBXProject section */ 187 | F4AE96DA25F2D2EE00019801 /* Project object */ = { 188 | isa = PBXProject; 189 | attributes = { 190 | LastSwiftUpdateCheck = 1250; 191 | LastUpgradeCheck = 1250; 192 | TargetAttributes = { 193 | F4AE96E125F2D2EE00019801 = { 194 | CreatedOnToolsVersion = 12.5; 195 | }; 196 | F4AE96F725F2D2EF00019801 = { 197 | CreatedOnToolsVersion = 12.5; 198 | TestTargetID = F4AE96E125F2D2EE00019801; 199 | }; 200 | }; 201 | }; 202 | buildConfigurationList = F4AE96DD25F2D2EE00019801 /* Build configuration list for PBXProject "CoreDataSync" */; 203 | compatibilityVersion = "Xcode 9.3"; 204 | developmentRegion = en; 205 | hasScannedForEncodings = 0; 206 | knownRegions = ( 207 | en, 208 | Base, 209 | ); 210 | mainGroup = F4AE96D925F2D2EE00019801; 211 | productRefGroup = F4AE96E325F2D2EE00019801 /* Products */; 212 | projectDirPath = ""; 213 | projectRoot = ""; 214 | targets = ( 215 | F4AE96E125F2D2EE00019801 /* CoreDataSync */, 216 | F4AE96F725F2D2EF00019801 /* CoreDataSyncTests */, 217 | ); 218 | }; 219 | /* End PBXProject section */ 220 | 221 | /* Begin PBXResourcesBuildPhase section */ 222 | F4AE96E025F2D2EE00019801 /* Resources */ = { 223 | isa = PBXResourcesBuildPhase; 224 | buildActionMask = 2147483647; 225 | files = ( 226 | F4AE96EA25F2D2EF00019801 /* Assets.xcassets in Resources */, 227 | ); 228 | runOnlyForDeploymentPostprocessing = 0; 229 | }; 230 | F4AE96F625F2D2EF00019801 /* Resources */ = { 231 | isa = PBXResourcesBuildPhase; 232 | buildActionMask = 2147483647; 233 | files = ( 234 | ); 235 | runOnlyForDeploymentPostprocessing = 0; 236 | }; 237 | /* End PBXResourcesBuildPhase section */ 238 | 239 | /* Begin PBXSourcesBuildPhase section */ 240 | F4AE96DE25F2D2EE00019801 /* Sources */ = { 241 | isa = PBXSourcesBuildPhase; 242 | buildActionMask = 2147483647; 243 | files = ( 244 | F4AE96EF25F2D2EF00019801 /* PersistenceController.swift in Sources */, 245 | F4AE96F225F2D2EF00019801 /* CoreDataSync.xcdatamodeld in Sources */, 246 | F4AE971F25F684B000019801 /* ImagePicker.swift in Sources */, 247 | F4C904F926138E7E00252717 /* Config.swift in Sources */, 248 | F4AE96E825F2D2EE00019801 /* ContentView.swift in Sources */, 249 | F4AE972125F69E7F00019801 /* AddContactView.swift in Sources */, 250 | F4AE96E625F2D2EE00019801 /* CoreDataSyncApp.swift in Sources */, 251 | ); 252 | runOnlyForDeploymentPostprocessing = 0; 253 | }; 254 | F4AE96F425F2D2EF00019801 /* Sources */ = { 255 | isa = PBXSourcesBuildPhase; 256 | buildActionMask = 2147483647; 257 | files = ( 258 | F4AE96FD25F2D2EF00019801 /* CoreDataSyncTests.swift in Sources */, 259 | ); 260 | runOnlyForDeploymentPostprocessing = 0; 261 | }; 262 | /* End PBXSourcesBuildPhase section */ 263 | 264 | /* Begin PBXTargetDependency section */ 265 | F4AE96FA25F2D2EF00019801 /* PBXTargetDependency */ = { 266 | isa = PBXTargetDependency; 267 | target = F4AE96E125F2D2EE00019801 /* CoreDataSync */; 268 | targetProxy = F4AE96F925F2D2EF00019801 /* PBXContainerItemProxy */; 269 | }; 270 | /* End PBXTargetDependency section */ 271 | 272 | /* Begin XCBuildConfiguration section */ 273 | F4AE970A25F2D2EF00019801 /* Debug */ = { 274 | isa = XCBuildConfiguration; 275 | buildSettings = { 276 | ALWAYS_SEARCH_USER_PATHS = NO; 277 | CLANG_ANALYZER_NONNULL = YES; 278 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 279 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 280 | CLANG_CXX_LIBRARY = "libc++"; 281 | CLANG_ENABLE_MODULES = YES; 282 | CLANG_ENABLE_OBJC_ARC = YES; 283 | CLANG_ENABLE_OBJC_WEAK = YES; 284 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 285 | CLANG_WARN_BOOL_CONVERSION = YES; 286 | CLANG_WARN_COMMA = YES; 287 | CLANG_WARN_CONSTANT_CONVERSION = YES; 288 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 289 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 290 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 291 | CLANG_WARN_EMPTY_BODY = YES; 292 | CLANG_WARN_ENUM_CONVERSION = YES; 293 | CLANG_WARN_INFINITE_RECURSION = YES; 294 | CLANG_WARN_INT_CONVERSION = YES; 295 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 296 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 297 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 298 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 299 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 300 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 301 | CLANG_WARN_STRICT_PROTOTYPES = YES; 302 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 303 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 304 | CLANG_WARN_UNREACHABLE_CODE = YES; 305 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 306 | COPY_PHASE_STRIP = NO; 307 | DEBUG_INFORMATION_FORMAT = dwarf; 308 | ENABLE_STRICT_OBJC_MSGSEND = YES; 309 | ENABLE_TESTABILITY = YES; 310 | GCC_C_LANGUAGE_STANDARD = gnu11; 311 | GCC_DYNAMIC_NO_PIC = NO; 312 | GCC_NO_COMMON_BLOCKS = YES; 313 | GCC_OPTIMIZATION_LEVEL = 0; 314 | GCC_PREPROCESSOR_DEFINITIONS = ( 315 | "DEBUG=1", 316 | "$(inherited)", 317 | ); 318 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 319 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 320 | GCC_WARN_UNDECLARED_SELECTOR = YES; 321 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 322 | GCC_WARN_UNUSED_FUNCTION = YES; 323 | GCC_WARN_UNUSED_VARIABLE = YES; 324 | IPHONEOS_DEPLOYMENT_TARGET = 14.1; 325 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 326 | MTL_FAST_MATH = YES; 327 | ONLY_ACTIVE_ARCH = YES; 328 | SDKROOT = iphoneos; 329 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 330 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 331 | }; 332 | name = Debug; 333 | }; 334 | F4AE970B25F2D2EF00019801 /* Release */ = { 335 | isa = XCBuildConfiguration; 336 | buildSettings = { 337 | ALWAYS_SEARCH_USER_PATHS = NO; 338 | CLANG_ANALYZER_NONNULL = YES; 339 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 340 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 341 | CLANG_CXX_LIBRARY = "libc++"; 342 | CLANG_ENABLE_MODULES = YES; 343 | CLANG_ENABLE_OBJC_ARC = YES; 344 | CLANG_ENABLE_OBJC_WEAK = YES; 345 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 346 | CLANG_WARN_BOOL_CONVERSION = YES; 347 | CLANG_WARN_COMMA = YES; 348 | CLANG_WARN_CONSTANT_CONVERSION = YES; 349 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 350 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 351 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 352 | CLANG_WARN_EMPTY_BODY = YES; 353 | CLANG_WARN_ENUM_CONVERSION = YES; 354 | CLANG_WARN_INFINITE_RECURSION = YES; 355 | CLANG_WARN_INT_CONVERSION = YES; 356 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 357 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 358 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 359 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 360 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 361 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 362 | CLANG_WARN_STRICT_PROTOTYPES = YES; 363 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 364 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 365 | CLANG_WARN_UNREACHABLE_CODE = YES; 366 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 367 | COPY_PHASE_STRIP = NO; 368 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 369 | ENABLE_NS_ASSERTIONS = NO; 370 | ENABLE_STRICT_OBJC_MSGSEND = YES; 371 | GCC_C_LANGUAGE_STANDARD = gnu11; 372 | GCC_NO_COMMON_BLOCKS = YES; 373 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 374 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 375 | GCC_WARN_UNDECLARED_SELECTOR = YES; 376 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 377 | GCC_WARN_UNUSED_FUNCTION = YES; 378 | GCC_WARN_UNUSED_VARIABLE = YES; 379 | IPHONEOS_DEPLOYMENT_TARGET = 14.1; 380 | MTL_ENABLE_DEBUG_INFO = NO; 381 | MTL_FAST_MATH = YES; 382 | SDKROOT = iphoneos; 383 | SWIFT_COMPILATION_MODE = wholemodule; 384 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 385 | VALIDATE_PRODUCT = YES; 386 | }; 387 | name = Release; 388 | }; 389 | F4AE970D25F2D2EF00019801 /* Debug */ = { 390 | isa = XCBuildConfiguration; 391 | buildSettings = { 392 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 393 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 394 | CODE_SIGN_ENTITLEMENTS = CoreDataSync/App/CoreDataSync.entitlements; 395 | CODE_SIGN_STYLE = Automatic; 396 | DEVELOPMENT_TEAM = ""; 397 | ENABLE_PREVIEWS = YES; 398 | INFOPLIST_FILE = CoreDataSync/App/Info.plist; 399 | LD_RUNPATH_SEARCH_PATHS = ( 400 | "$(inherited)", 401 | "@executable_path/Frameworks", 402 | ); 403 | PRODUCT_BUNDLE_IDENTIFIER = com.apple.samples.cloudkit.CoreDataSync; 404 | PRODUCT_NAME = "$(TARGET_NAME)"; 405 | SWIFT_VERSION = 5.0; 406 | TARGETED_DEVICE_FAMILY = "1,2"; 407 | }; 408 | name = Debug; 409 | }; 410 | F4AE970E25F2D2EF00019801 /* Release */ = { 411 | isa = XCBuildConfiguration; 412 | buildSettings = { 413 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 414 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 415 | CODE_SIGN_ENTITLEMENTS = CoreDataSync/App/CoreDataSync.entitlements; 416 | CODE_SIGN_STYLE = Automatic; 417 | DEVELOPMENT_TEAM = ""; 418 | ENABLE_PREVIEWS = YES; 419 | INFOPLIST_FILE = CoreDataSync/App/Info.plist; 420 | LD_RUNPATH_SEARCH_PATHS = ( 421 | "$(inherited)", 422 | "@executable_path/Frameworks", 423 | ); 424 | PRODUCT_BUNDLE_IDENTIFIER = com.apple.samples.cloudkit.CoreDataSync; 425 | PRODUCT_NAME = "$(TARGET_NAME)"; 426 | SWIFT_VERSION = 5.0; 427 | TARGETED_DEVICE_FAMILY = "1,2"; 428 | }; 429 | name = Release; 430 | }; 431 | F4AE971025F2D2EF00019801 /* Debug */ = { 432 | isa = XCBuildConfiguration; 433 | buildSettings = { 434 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 435 | BUNDLE_LOADER = "$(TEST_HOST)"; 436 | CODE_SIGN_STYLE = Automatic; 437 | INFOPLIST_FILE = CoreDataSyncTests/Info.plist; 438 | LD_RUNPATH_SEARCH_PATHS = ( 439 | "$(inherited)", 440 | "@executable_path/Frameworks", 441 | "@loader_path/Frameworks", 442 | ); 443 | PRODUCT_BUNDLE_IDENTIFIER = com.apple.samples.cloudkit.CoreDataSyncTests; 444 | PRODUCT_NAME = "$(TARGET_NAME)"; 445 | SWIFT_VERSION = 5.0; 446 | TARGETED_DEVICE_FAMILY = "1,2"; 447 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CoreDataSync.app/CoreDataSync"; 448 | }; 449 | name = Debug; 450 | }; 451 | F4AE971125F2D2EF00019801 /* Release */ = { 452 | isa = XCBuildConfiguration; 453 | buildSettings = { 454 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 455 | BUNDLE_LOADER = "$(TEST_HOST)"; 456 | CODE_SIGN_STYLE = Automatic; 457 | INFOPLIST_FILE = CoreDataSyncTests/Info.plist; 458 | LD_RUNPATH_SEARCH_PATHS = ( 459 | "$(inherited)", 460 | "@executable_path/Frameworks", 461 | "@loader_path/Frameworks", 462 | ); 463 | PRODUCT_BUNDLE_IDENTIFIER = com.apple.samples.cloudkit.CoreDataSyncTests; 464 | PRODUCT_NAME = "$(TARGET_NAME)"; 465 | SWIFT_VERSION = 5.0; 466 | TARGETED_DEVICE_FAMILY = "1,2"; 467 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CoreDataSync.app/CoreDataSync"; 468 | }; 469 | name = Release; 470 | }; 471 | /* End XCBuildConfiguration section */ 472 | 473 | /* Begin XCConfigurationList section */ 474 | F4AE96DD25F2D2EE00019801 /* Build configuration list for PBXProject "CoreDataSync" */ = { 475 | isa = XCConfigurationList; 476 | buildConfigurations = ( 477 | F4AE970A25F2D2EF00019801 /* Debug */, 478 | F4AE970B25F2D2EF00019801 /* Release */, 479 | ); 480 | defaultConfigurationIsVisible = 0; 481 | defaultConfigurationName = Release; 482 | }; 483 | F4AE970C25F2D2EF00019801 /* Build configuration list for PBXNativeTarget "CoreDataSync" */ = { 484 | isa = XCConfigurationList; 485 | buildConfigurations = ( 486 | F4AE970D25F2D2EF00019801 /* Debug */, 487 | F4AE970E25F2D2EF00019801 /* Release */, 488 | ); 489 | defaultConfigurationIsVisible = 0; 490 | defaultConfigurationName = Release; 491 | }; 492 | F4AE970F25F2D2EF00019801 /* Build configuration list for PBXNativeTarget "CoreDataSyncTests" */ = { 493 | isa = XCConfigurationList; 494 | buildConfigurations = ( 495 | F4AE971025F2D2EF00019801 /* Debug */, 496 | F4AE971125F2D2EF00019801 /* Release */, 497 | ); 498 | defaultConfigurationIsVisible = 0; 499 | defaultConfigurationName = Release; 500 | }; 501 | /* End XCConfigurationList section */ 502 | 503 | /* Begin XCVersionGroup section */ 504 | F4AE96F025F2D2EF00019801 /* CoreDataSync.xcdatamodeld */ = { 505 | isa = XCVersionGroup; 506 | children = ( 507 | F4AE96F125F2D2EF00019801 /* CoreDataSync.xcdatamodel */, 508 | ); 509 | currentVersion = F4AE96F125F2D2EF00019801 /* CoreDataSync.xcdatamodel */; 510 | path = CoreDataSync.xcdatamodeld; 511 | sourceTree = ""; 512 | versionGroupType = wrapper.xcdatamodel; 513 | }; 514 | /* End XCVersionGroup section */ 515 | }; 516 | rootObject = F4AE96DA25F2D2EE00019801 /* Project object */; 517 | } 518 | -------------------------------------------------------------------------------- /CoreDataSync/App/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /CoreDataSync/App/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /CoreDataSync/App/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /CoreDataSync/App/Config.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Config.swift 3 | // CoreDataSync 4 | // 5 | 6 | enum Config { 7 | /// iCloud container identifier. 8 | /// Update this if you wish to use your own iCloud container. 9 | static let containerIdentifier = "iCloud.com.apple.samples.cloudkit.CoreDataSync" 10 | } 11 | -------------------------------------------------------------------------------- /CoreDataSync/App/CoreDataSync.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | aps-environment 6 | development 7 | com.apple.developer.icloud-container-identifiers 8 | 9 | iCloud.com.apple.samples.cloudkit.CoreDataSync 10 | 11 | com.apple.developer.icloud-services 12 | 13 | CloudKit 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /CoreDataSync/App/CoreDataSyncApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CoreDataSyncApp.swift 3 | // CoreDataSync 4 | // 5 | 6 | import SwiftUI 7 | 8 | @main 9 | struct CoreDataSyncApp: App { 10 | private let persistenceController = PersistenceController.shared 11 | 12 | var body: some Scene { 13 | WindowGroup { 14 | ContentView() 15 | .environment(\.managedObjectContext, persistenceController.container.viewContext) 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /CoreDataSync/App/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | 28 | UIApplicationSupportsIndirectInputEvents 29 | 30 | UIBackgroundModes 31 | 32 | remote-notification 33 | 34 | UILaunchScreen 35 | 36 | UIRequiredDeviceCapabilities 37 | 38 | armv7 39 | 40 | UISupportedInterfaceOrientations 41 | 42 | UIInterfaceOrientationPortrait 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | UISupportedInterfaceOrientations~ipad 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationPortraitUpsideDown 50 | UIInterfaceOrientationLandscapeLeft 51 | UIInterfaceOrientationLandscapeRight 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /CoreDataSync/Core Data/CoreDataSync.xcdatamodeld/.xccurrentversion: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | _XCCurrentVersionName 6 | CoreDataSync.xcdatamodel 7 | 8 | 9 | -------------------------------------------------------------------------------- /CoreDataSync/Core Data/CoreDataSync.xcdatamodeld/CoreDataSync.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /CoreDataSync/Core Data/PersistenceController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PersistenceController.swift 3 | // CoreDataSync 4 | // 5 | 6 | import CoreData 7 | import UIKit 8 | 9 | struct PersistenceController { 10 | static let shared = PersistenceController() 11 | 12 | static var preview: PersistenceController = { 13 | let result = PersistenceController() 14 | let viewContext = result.container.viewContext 15 | for i in 0 ..< 10 { 16 | let newContact = Contact(context: viewContext) 17 | newContact.name = "Contact #\(i)" 18 | newContact.photo = UIImage(systemName: "multiply.circle.fill") 19 | } 20 | do { 21 | try viewContext.save() 22 | } catch { 23 | // Replace this implementation with code to handle the error appropriately. 24 | // 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. 25 | let nsError = error as NSError 26 | fatalError("Unresolved error \(nsError), \(nsError.userInfo)") 27 | } 28 | return result 29 | }() 30 | 31 | let container: NSPersistentCloudKitContainer = { 32 | let container = NSPersistentCloudKitContainer(name: "CoreDataSync") 33 | container.viewContext.automaticallyMergesChangesFromParent = true 34 | container.loadPersistentStores(completionHandler: { (storeDescription, error) in 35 | if let error = error as NSError? { 36 | // Replace this implementation with code to handle the error appropriately. 37 | // 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. 38 | 39 | /* 40 | Typical reasons for an error here include: 41 | * The parent directory does not exist, cannot be created, or disallows writing. 42 | * The persistent store is not accessible, due to permissions or data protection when the device is locked. 43 | * The device is out of space. 44 | * The store could not be migrated to the current model version. 45 | Check the error message to determine what the actual problem was. 46 | */ 47 | fatalError("Unresolved error \(error), \(error.userInfo)") 48 | } 49 | }) 50 | 51 | return container 52 | }() 53 | } 54 | -------------------------------------------------------------------------------- /CoreDataSync/Views/AddContactView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AddContactView.swift 3 | // CoreDataSync 4 | // 5 | 6 | import Foundation 7 | import SwiftUI 8 | 9 | /// View for adding new contacts. 10 | struct AddContactView: View { 11 | @Environment(\.presentationMode) var presentationMode 12 | @State private var image: UIImage? 13 | @State private var isShowingImagePicker: Bool = false 14 | @State private var nameInput: String = "" 15 | 16 | /// Callback after user selects to add contact with given name and image. 17 | let onAdd: ((String, UIImage?) -> Void)? 18 | /// Callback after user cancels. 19 | let onCancel: (() -> Void)? 20 | 21 | var body: some View { 22 | NavigationView { 23 | VStack { 24 | HStack { 25 | contactImage.onTapGesture { 26 | self.isShowingImagePicker = true 27 | } 28 | TextField("Full Name", text: $nameInput) 29 | .textContentType(.name) 30 | } 31 | Spacer() 32 | } 33 | .padding() 34 | .navigationTitle("Add Contact") 35 | .sheet(isPresented: $isShowingImagePicker, onDismiss: { 36 | isShowingImagePicker = false 37 | }, content: { 38 | ImagePicker(selectedImage: $image) 39 | }) 40 | .toolbar { 41 | ToolbarItem(placement: .cancellationAction) { 42 | Button("Cancel", action: { onCancel?() }) 43 | } 44 | ToolbarItem(placement: .confirmationAction) { 45 | Button("Add", action: { onAdd?(nameInput, image) }) 46 | .disabled(nameInput.isEmpty) 47 | } 48 | } 49 | } 50 | } 51 | 52 | var contactImage: some View { 53 | if let image = image { 54 | return AnyView(Image(uiImage: image) 55 | .resizable() 56 | .scaledToFill() 57 | .frame(width: 64, height: 64, alignment: .center) 58 | .clipped()) 59 | } else { 60 | return AnyView(Image(systemName: "photo") 61 | .resizable() 62 | .aspectRatio(contentMode: .fit) 63 | .frame(width: 64, height: 64, alignment: .center) 64 | .foregroundColor(.gray)) 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /CoreDataSync/Views/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // CoreDataSync 4 | // 5 | 6 | import SwiftUI 7 | import CoreData 8 | 9 | struct ContentView: View { 10 | @Environment(\.managedObjectContext) private var viewContext 11 | 12 | @FetchRequest( 13 | sortDescriptors: [NSSortDescriptor(keyPath: \Contact.name, ascending: true)], 14 | animation: .default) 15 | private var contacts: FetchedResults 16 | 17 | @State private var isShowingAddView = false 18 | 19 | var body: some View { 20 | NavigationView { 21 | List { 22 | ForEach(contacts) { contact in 23 | HStack { 24 | if let image = contact.photo { 25 | Image(uiImage: image) 26 | .resizable() 27 | .scaledToFill() 28 | .frame(width: 64, height: 64, alignment: .center) 29 | .clipped() 30 | } else { 31 | Image(systemName: "person.circle") 32 | .resizable() 33 | .aspectRatio(contentMode: .fit) 34 | .frame(width: 64, height: 64, alignment: .center) 35 | .foregroundColor(.gray) 36 | } 37 | Text(contact.name ?? "None") 38 | } 39 | } 40 | .onDelete(perform: deleteContacts) 41 | } 42 | .navigationTitle("Core Data Contacts") 43 | .toolbar { 44 | ToolbarItem(placement: .primaryAction) { 45 | Button(action: { isShowingAddView = true }) { Image(systemName: "plus") } 46 | } 47 | } 48 | } 49 | .sheet(isPresented: $isShowingAddView, content: { 50 | AddContactView(onAdd: { name, image in 51 | isShowingAddView = false 52 | addContact(name: name, photo: image) 53 | }, onCancel: { isShowingAddView = false }) 54 | }) 55 | } 56 | 57 | private func addContact(name: String, photo: UIImage?) { 58 | let newContact = Contact(context: viewContext) 59 | newContact.name = name 60 | newContact.photo = photo 61 | 62 | do { 63 | try viewContext.save() 64 | } catch { 65 | fatalError("Error: \(error)") 66 | } 67 | } 68 | 69 | private func deleteContacts(offsets: IndexSet) { 70 | offsets.map { contacts[$0] }.forEach(viewContext.delete) 71 | 72 | do { 73 | try viewContext.save() 74 | } catch { 75 | let nsError = error as NSError 76 | fatalError("Unresolved error \(nsError), \(nsError.userInfo)") 77 | } 78 | } 79 | } 80 | 81 | struct ContentView_Previews: PreviewProvider { 82 | static var previews: some View { 83 | ContentView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /CoreDataSync/Views/ImagePicker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImagePicker.swift 3 | // CoreDataSync 4 | // 5 | 6 | import Foundation 7 | import SwiftUI 8 | 9 | /// This struct wraps a `UIImagePickerController` for use in SwiftUI. 10 | struct ImagePicker: UIViewControllerRepresentable { 11 | @Binding var selectedImage: UIImage? 12 | @Environment(\.presentationMode) var presentationMode 13 | 14 | func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {} 15 | 16 | func makeUIViewController(context: Context) -> some UIViewController { 17 | let imagePickerController = UIImagePickerController() 18 | imagePickerController.sourceType = .photoLibrary 19 | imagePickerController.delegate = context.coordinator 20 | return imagePickerController 21 | } 22 | 23 | func makeCoordinator() -> ImagePicker.Coordinator { 24 | Coordinator(parent: self) 25 | } 26 | 27 | final class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate { 28 | let parent: ImagePicker 29 | 30 | init(parent: ImagePicker) { 31 | self.parent = parent 32 | } 33 | 34 | func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) { 35 | DispatchQueue.main.async { 36 | self.parent.selectedImage = (info[.editedImage] ?? info[.originalImage]) as? UIImage 37 | self.parent.presentationMode.wrappedValue.dismiss() 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /CoreDataSyncTests/CoreDataSyncTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CoreDataSyncTests.swift 3 | // CoreDataSyncTests 4 | // 5 | 6 | import XCTest 7 | import CloudKit 8 | @testable import CoreDataSync 9 | 10 | class CoreDataSyncTests: XCTestCase { 11 | 12 | func test_CloudKitReadiness() throws { 13 | // Fetch zones from the Private Database of the CKContainer for the current user to test for valid/ready state 14 | let container = CKContainer(identifier: Config.containerIdentifier) 15 | let database = container.privateCloudDatabase 16 | 17 | let fetchExpectation = expectation(description: "Expect CloudKit fetch to complete") 18 | database.fetchAllRecordZones { _, error in 19 | if let error = error as? CKError { 20 | switch error.code { 21 | case .badContainer, .badDatabase: 22 | XCTFail("Create or select a CloudKit container in this app target's Signing & Capabilities in Xcode") 23 | 24 | case .permissionFailure, .notAuthenticated: 25 | XCTFail("Simulator or device running this app needs a signed-in iCloud account") 26 | 27 | default: 28 | XCTFail("CKError: \(error)") 29 | } 30 | } 31 | fetchExpectation.fulfill() 32 | } 33 | 34 | waitForExpectations(timeout: 10) 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /CoreDataSyncTests/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 | 22 | 23 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright © 2022 Apple Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CloudKit Samples: Core Data with CloudKit 2 | 3 | ### Goals 4 | 5 | This project demonstrates using Core Data with `NSPersistentCloudKitContainer`, which encapsulates the Core Data stack in an application and mirrors persistent stores to a CloudKit private database. Changes made locally are automatically sent to the remote CloudKit database, and remote changes are fetched and brought into local stores across devices through background push notifications. 6 | 7 | ### Prerequisites 8 | 9 | * A Mac with [Xcode 12](https://developer.apple.com/xcode/) (or later) installed is required to build and test this project. 10 | * An iOS device which will receive CloudKit change notifications is required to install and run the app on, as simulators cannot receive remote push notifications. 11 | * An active [Apple Developer Program membership](https://developer.apple.com/support/compare-memberships/) is needed to create a CloudKit container and sign the app to run on a device. 12 | 13 | ### Setup Instructions 14 | 15 | 1. Ensure you are logged into your developer account in Xcode with an active membership. 16 | 1. In the “Signing & Capabilities” tab of the CoreDataSync target, ensure your team is selected in the Signing section, and there is a valid container selected under the “iCloud” section. 17 | 1. Ensure that both the simulator you wish to use and the device you will run the app on are logged into the same iCloud account. 18 | 19 | #### Using Your Own iCloud Container 20 | 21 | * Create a new iCloud container through Xcode’s “Signing & Capabilities” tab of the CoreDataSync app target. 22 | * Update the `containerIdentifier` property in [Config.swift](CoreDataSync/App/Config.swift) with your new iCloud container ID. 23 | 24 | ### How it Works 25 | 26 | * The `CoreDataSync` model defines a `Contact` entity, which stores a name (`String`) and photo (`UIImage` Transformable). 27 | * The `PersistenceController` class creates an `NSPersistentCloudKitContainer` object with the `CoreDataSync` model. 28 | * The main list view uses the [`@FetchRequest`](https://developer.apple.com/documentation/swiftui/fetchrequest) property wrapper to retrieve Contact objects from the Core Data store. 29 | * Creating new Contacts and deleting existing Contacts is done through normal Core Data operations. 30 | * `NSPersistentCloudKitContainer` syncs with the user’s private database in the iCloud container listed in the app’s entitlements file. 31 | 32 | ### Example Flow 33 | 34 | 1. Run the app on a device. After initializing the store, CoreData+CloudKit mirroring will fetch any existing records from the remote private database. 35 | 1. Repeat the above on a simulator, and add a new contact through the UI. 36 | 1. The device receives a background push notification which is automatically processed by CoreData+CloudKit. Changes are fetched and merged into the local store, and both simulator and device once again show the same content. Deleting a contact by swiping left on a cell produces similar behavior. 37 | 38 | Note that because remote push notifications are not supported on simulators, changes on a running device will not be automatically reflected on a running simulator as they are the other way around. Running the app again on a simulator will cause CoreData+CloudKit to sync changes made on a device. 39 | 40 | ### Things To Learn 41 | 42 | * Showing a live list of Core Data managed objects with SwiftUI using the `@FetchRequest` property wrapper. 43 | * Adding and deleting objects with `NSManagedObjectContext`. 44 | * Automatic syncing of Core Data objects with `NSPersistentCloudKitContainer`. 45 | * `NSPersistentCloudKitContainer` processing of push notifications to receive and display updates initiated on another device. 46 | * Storage and decoding of image types in Core Data and CloudKit Private Database. 47 | 48 | ### Further Reading 49 | 50 | * [NSPersistentCloudKitContainer](https://developer.apple.com/documentation/coredata/nspersistentcloudkitcontainer) 51 | * [Mirroring a Core Data Store with CloudKit](https://developer.apple.com/documentation/coredata/mirroring_a_core_data_store_with_cloudkit) 52 | --------------------------------------------------------------------------------