{"id":13694137,"url":"https://github.com/jazzychad/iCloudCoreDataStarter","last_synced_at":"2025-05-03T01:31:24.313Z","repository":{"id":44464726,"uuid":"411827651","full_name":"jazzychad/iCloudCoreDataStarter","owner":"jazzychad","description":"Example Xcode swift iOS project for Core Data + iCloud syncing","archived":false,"fork":false,"pushed_at":"2022-01-14T15:53:04.000Z","size":936,"stargazers_count":537,"open_issues_count":1,"forks_count":25,"subscribers_count":12,"default_branch":"main","last_synced_at":"2024-11-12T20:46:48.494Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jazzychad.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-09-29T20:59:23.000Z","updated_at":"2024-10-22T11:05:05.000Z","dependencies_parsed_at":"2022-08-02T18:15:19.316Z","dependency_job_id":null,"html_url":"https://github.com/jazzychad/iCloudCoreDataStarter","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jazzychad%2FiCloudCoreDataStarter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jazzychad%2FiCloudCoreDataStarter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jazzychad%2FiCloudCoreDataStarter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jazzychad%2FiCloudCoreDataStarter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jazzychad","download_url":"https://codeload.github.com/jazzychad/iCloudCoreDataStarter/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252130400,"owners_count":21699083,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-02T17:01:25.215Z","updated_at":"2025-05-03T01:31:23.294Z","avatar_url":"https://github.com/jazzychad.png","language":"Swift","readme":"\n# iCloudCoreDataStarter\n\nHello, I'm Chad. For the last several months I have been working on\n[Sticker Doodle, an app you should go download right\nnow!](https://stickerdoodle.app)\n\nIn the course of building [Sticker Doodle](https://stickerdoodle.app)\n(which you should go download right now), I ran into many brick walls\nand learned way too much about Core Data, iCloud sync, Collection and\nTable Views, and Diffable Data Sources.\n\nThere is documentation for each of those individually, but I could\nfind no clear and simple example project that ties them all together\nin a neat little bow.\n\nWell, that changes today.\n\n**DISCLAIMER: This repo is for educational purposes. While I believe\n  that the code inside is production-ready, you should always read and\n  thoroughly audit any code you ship in a production application.**\n\nThis is an example Xcode iOS swift project that demonstrates using the\nfollowing technologies:\n\n- [X] Core Data\n- [X] iCloud sync\n- [X] Collection Views\n- [X] Table Views\n- [X] App Groups\n\nIt aims to implement a working app with the minimum amount of code to\naccomplish the following features:\n\n- [X] UICollectionView/UITableView\n- [X] ... using diffable data source\n- [X] ... with Core Data's NSFetchedResultsController\n- [X] Reloading cells on object update\n- [X] Collection View cell context menu\n- [X] Object insertion\n- [X] Object deletion\n- [X] Object updating\n- [X] Collection View multi-selection toggling\n- [X] Collection View drag-and-drop redording of objects\n- [X] Syncing Core Data state between app and extensions (in real time)\n- [X] iCloud syncing of Core Data between devices\n- [X] Core Data value transformers\n\n### What this project is NOT:\n\n- [ ] a Core Data tutorial\n- [ ] an iCloud/CloudKit tutorial\n- [ ] free tech support\n\n### Training\n\nIf you are interested in having a focused (paid) training session for\nyour dev team about this topic, please contact:\n\n**support at jazzychad dot net**\n\n### Prerequisites\n\n- A moderate familiarity with how Core Data works and how to setup a Core Data database/model\n- A moderate familiarity with setting up and dealing with iCloud containers\n- The various configuration and settings screens in Xcode\n\n### SDK Requirements\n\nThis project uses only APIs availble in **iOS 14 and earlier**\n(i.e. there are _no_ iOS 15 APIs present in this project).\n\n### Using this project\n\nYou should be able to clone this repo and compile/run the\n`iCloudCoreDataStarter` scheme _after setting your Development Team_\nin the `Signing \u0026 Capabilities` tab for the `iCloudCoreDataStarter`\nand `iMessageApp` targets.\n\nBuilding in the `DEBUG` configuration and using `Automatically manage\nsigning` should allow you to run this example on simulators and local\ndevelopment devices.\n\nProvisioning for a `RELEASE` build is beyond the scope of this project.\n\n# Here We Go\n\n## For New Projects\n\nIf you are creating a new project and want to use Core Data and iCloud\nsync, make sure to check the two relevant boxes at the bottom of the\ndialog:\n\n![New Project](https://raw.githubusercontent.com/jazzychad/iCloudCoreDataStarter/main/screenshots/NewProject.png)\n\nThis has the advantage of creating the necessary `.xcdatamodeld` file\nin your project to setup your database models, but it also creates\nsome boilerplate Core Data code in the `AppDelegate` which you can\nremove if you plan on using the files in this example in other\nprojects.\n\n## Core Data Model\n\nIn this example app, we are mainly concerned with the creation and\ndisplay of `Thing` objects. `Thing`s have two primary properties that\nmake a thing a thing:\n\n- `amount: Int64` - represents some quantity, I leave it up to your imagination\n- `color: UIColor` - makes the thing a little more visually appealing\n\nWhenever I make a Core Data model, I _always always always always_ add the following properties to it:\n\n- `createdAt: Date` - the date that this object was created (yes,\n  there is also a creationDate field in the CKRecord object that backs\n  this Core Data row, but in the cases where you are not backing your\n  Core Data with iCloud, having this date readily available is\n  extremely handy, so I have just made a habit of adding it).\n\n- `displayOrder: Int64` - this is important for determining in what\n  order to show objects in the UI and what we use to sort the fetch\n  request (along with the `createdAt` date).\n\n- `identifier: String` - some unique identifier for the object. This\n  could also be a `UUID` instead. This isn't strictly necessary in all\n  use-cases, but it comes in really handy when you eventually run into\n  a situation where you need to have it.\n\nOur final example `Thing` model looks like this:\n\n![Thing object model](https://raw.githubusercontent.com/jazzychad/iCloudCoreDataStarter/main/screenshots/ThingModel.png)\n\n## App Group\n\nApp Groups allow your apps and their extensions to (among other\nthings) have a shared space on disk to read and write data. This is\ncrucial for sharing data between apps and extensions, but it is\nespecially good for sharing a Core Data store between apps and\nextensions.\n\nAn app group (`group.com.example.iCloudCoreDataStarter`) is created\nand added to each app/extension target in the `Signing \u0026 Capabilities`\ntab in Xcode.\n\nApp Group identifiers are scoped to your developer account, so you can\nuse the one in this example project if you like.\n\n\n![App Group setup](https://raw.githubusercontent.com/jazzychad/iCloudCoreDataStarter/main/screenshots/AppGroup.png)\n\n## iCloud container\n\nTo enable iCloud sync for Core Data, you must create an iCloud\ncontainer (`iCloud.com.example.iCloudCoreDataStarter.iCloud` in this\nexample project) and enable the CloudKit service in the `Signing \u0026\nCapabilities` tab in Xcode.\n\niCloud container identifiers are scoped to your developer account, so\nyou can use the one in this example project if you like.\n\nAdding CloudKit will typically also enable the `Push Notification`\ncapability for you. There is _no other configuration you need to do\nfor push notifications to work with CloudKit_ - it just works.\n\n_However_ you will also need to add the `Background Modes` capability\nin the `Signing \u0026 Capabilities` tab and select `Remote notifications`.\n\n**IMPORTANT!! Always remember to publish your iCloud container schema\n  to _Production_ before you publish your app to TestFlight or the App\n  Store!!**\n\n![iCloud container](https://raw.githubusercontent.com/jazzychad/iCloudCoreDataStarter/main/screenshots/iCloudContainer.png)\n\n### Note about provisioning\n\nXcode will typically take care of creating the App Group and iCloud\ncontainer and updating the provisioning profiles for you if you are\nbuilding in Debug mode, but in a Release build you will probably need\nto create specific provisioning profiles for your apps/extensions that\nhave the right entitlements.\n\n\n## CloudKit and Core Data logging\n\nBy default CloudKit will log _a giant amount_ of information to the\nconsole and stderr. This can sometimes be useful to debug certain\nissues, but there is so much text that it will get in the way of other\nlogging you may be doing in your app. You can suppress this output\nwith the following Run Arguments in your Scheme:\n\n```\n-com.apple.CoreData.CloudKitDebug 0\n-com.apple.CoreData.Logging.stderr 0\n-com.apple.CoreData.SQLDebug 0\n```\n\n![Run Arguments](https://raw.githubusercontent.com/jazzychad/iCloudCoreDataStarter/main/screenshots/RunArguments.png)\n\n\n# CoreDataStack\n\nThe bulk of the Core Data logic lives in the `CoreDataStack` folder of\nthe project. These files were designed and written such that there is\nnothing specific about them to this example project, i.e. they could\nbe copied into another project and re-used as-is.\n\nLet's take a look at what each file does:\n\n## CoreDataStack.swift\n\nHandles the following jobs:\n\n- [X] Provides a `CoreDataStack.shared` object for dealing with Core Data objects throughout the app\n- [X] Create and configure an NSPersistentCloudKitContainer with appropriate settings\n- [X] Creates the Core Data store file in the App Group on-disk location\n- [X] Handles Core Data persistent history tracking and updating\n\nBefore using the `CoreDataStack.shared` object, you _must_ provide a\nconfiguration object which will add proper configuration to the Core\nData stack. As early as you can in your app/extension lifecycle, you\nmust call:\n\n```swift\nlet config = CoreDataStackConfig(...)\nconfigureCoreDataStack(withConfig: config)\n```\n\nThe `authorName` property on `CoreDataStack` is also very\nimportant. It is a way to tell which app/extension generated\ntransactions into the Core Data store. You _must_ set this as early as\npossible in the app/extension lifecycle. For example, in the main app:\n\n```swift\nCoreDataStack.shared.authorName = \"app\"\n```\n\nand in, for example, a Messages app extension:\n\n```swift\nCoreDataStack.shared.authorName = \"iMessageApp\"\n```\n\nThe author name of the data store transactions help each process\nfilter out which persistent history transactions need to be replayed\ninto the current managed object context.\n\nIn this example project, you will see that both the configuration and\nauthor name are set in\n`AppDelegate.application(_:didFinishLaunchingWithOptions:)` in the\nmain app, and in the `fetchedResultController` initializer in\n`iMsgThingTableViewController` of the Messages app extension.\n\n---\n\nLet's talk about the `CoreDataStack.coerceObjectIds(managedObjects:)`\nfunction. In an ideal world, this method shouldn't be needed at all,\nhowever I believe there is a nasty bug deep in the guts of the Core\nData framework which makes this function necessary.\n\nWhen an `NSManagedObject` is created and added to its\n`NSManagedObjectContext`, _but (crucially) before `.save()` is called\non the manged object context,_ the new managed object will have a\n_temporary_ objectID (which can be checked with\n`managedObject.objectID.isTemporaryID`).\n\nBefore `.save()` is called on the managed object context, this\ntemporary ID can be used to fetch the object, refer to it, etc... it\nacts like a normal `NSManagedObjectID` -- _HOWEVER,_ after `.save()`\nis called on the managed object context, 2 things are supposed to\nhappen:\n\n1. `NSManagedObjects` with temporary IDs are supposed to be assigned a\npermanent ID and those objects updated in memory with the new ID.\n\n2. The managed object context _forgets all temporary IDs_ and\nattempting to use them to identify an object will _fail._\n\nThere seems to be a bug (in iOS 14 and iOS 15 as of my latest testing)\nwhere _sometimes_ Step 1 will _not actually happen_ and newly inserted\nand saved objects will still have a temporary ID!! This has led to all\nsorts of unexpected and frustrating behavior until I figured out what\nwas actually going on. There are several developer forum posts and\nStack Overflow questions regarding the same behavior, so I am not the\nonly one that has experienced this bug.\n\nThus, this evil but necessary `coerceObjectIds(managedObjects:)` has\ncome into existence and is called whenever a new `NSManagedObject` is\ncreated and the managed object context is saved.\n\n## CoreDataUtilities.swift\n\nContains helper code for creating value transformers for Core Data\n\"transformable\" properties. There is an example of extending `UIColor`\nto be a value transformer.\n\nWhen you register a transformer in this way, make sure to set the\nTransformer value in the property inspector to the\n`valueTransformerName` you specify in the code:\n\n![UIColor Value Transformer](https://raw.githubusercontent.com/jazzychad/iCloudCoreDataStarter/main/screenshots/ValueTransformer.png)\n\n## CoreDataDistributedUpdateListener.swift\n\nThis class implements the CFNotification machinery needed to\ncommunicate between your app and extensions to notify each other of\nCore Data updates.\n\nYou should create one per process as early as possible. For example,\nin an app you should create a property in the AppDelegate.\n\n```swift\nlet coreDataDistributedUpdateListner = CoreDataDistributedUpdateListener()\n```\n\n## CoreDataDiffableFetchedResultsHandler.swift\n\n`CoreDataDiffableFetchedResultsHandler` is designed to be a\n`NSFetchedResultsController` delegate and handle the diffable\ndatasource machinery whether you are using\n`UITableViewDiffableDataSource` or\n`UICollectionViewDiffableDataSource`\n\nIt will take care of updating the diffable datasource snapshot from\nthe NSFetchedResultsController and deal with reloading cells for\nobjects which have been updated but not inserted or moved (which\notherwise do not automatically get reloaded).\n\nSee `ThingRootCollectionViewController.viewDidLoad()` and\n`iMsgThingTableViewController.viewDidLoad()` for example usage.\n\n\n# Other Files\n\nThe other project-specific files are covered here.\n\n## Thing+Extras.swift\n\nI find it very useful to have a file with Core Data object specific helper/utility methods.\n\nLet's talk about `ThingPrimitive` -- I also find it very useful to have a plain struct which represents the important fields of a Core Data model. There are some advantages to having them around:\n\n- NSManagedObjects are _not_ thread safe. They are _extremely thread\n  unsafe,_ in fact. Having a struct which acts as a \"bag of\n  properties\" that can be passed around threads as necessary can be\n  handy.\n\n- Creating a new NSManagedObject (a `Thing` for example) will fire off\n  NSFetchedResultsController delegate callbacks because creating the\n  object necessarily inserts the object into an\n  NSManagedObjectContext. This can be undesirable. For example, if you\n  had a screen to create or compose a new `Thing` object, and it is\n  not fully configured while the creation flow is happening, the root\n  collection view would show a half-baked or incomplete representation\n  of the `Thing`! In these situations, I like to have a `Primitive`\n  struct available to configure along the way, and then at the very\n  end, create the real NSManagedObject with the Primitive properties.\n\n## ThingRootCollectionViewController.swift\n\nThis is an example implementation of how to wire up the following\nvarious features in a UICollectionViewController:\n\n- [X] A `var fetchedResultController: NSFetchedResultsController\u003cThing\u003e` to retrieve the Thing objects to display\n- [X] A `UICollectionViewDiffableDataSource` to drive the collectionView's data\n- [X] A `CoreDataDiffableFetchedResultsHandler` to deal with fetchedResultsController delegate updates\n- [X] Drag and Drop re-ordering of Thing objects and the releated bookkeeping\n- [X] Long-press context menu generation for collectionView cells\n- [X] An example of how to handle toggling `.allowsMultipleSelection` for selecting and manipulating multiple cells\n\nIf you wanted to use this in your own app with your own Core Data\nmodel, I _think_ you should be able to find/replace `thing` and\n`Thing` as appropriate and have a pretty good starting point!\n\n## ThingViewController.swift\n\nA very simple view controller for viewing and updating existing Thing\nobjects. Demonstrates the use of `ThingPrimitive` to do an upsert when\ntapping the Save button.\n\n## iMsgThingTableViewController.swift\n\nThis is a UITableViewController inside the Messages App Extension\nwhich displays the Thing objects in the database in a tableView\n(instead of a collectionView). This is to demonstrate how similar it\ncan be to use the same basic patterns and the\n`CoreDataDiffableFetchedResultsHandler` to achieve the same behavior\nas in the `ThingRootCollectionViewController` example.\n\nYou can swipe to delete objects, and tapping on a row will randomly\nupdate the Thing object with new `amount` and `color` values (these\nwill be immediately reflected in the main app).\n\nLikewise, if you create/update/delete/move a Thing object in the main\napp, it will immediately be reflected in the Messages App\nExtension. This can be viewed happening in real-time by launching the\nmain app and the Messages App Extension in split-view on an iPad\nsimulator.\n\n![iPad Split View](https://raw.githubusercontent.com/jazzychad/iCloudCoreDataStarter/main/screenshots/iPadSplitView.png)\n\n# Issues\n\nThere might be bugs, or unclear documentation, or better ways to do\nsomething! Please open an issue (or send a PR) to help improve this\nexample project. I will also be updating it as I learn more.\n\n# Resources\n\nThe following were very useful in helping me figure out all of the\nknowledge included in this project. There are countless other websites\nand stack overflow answers lost to the sand of frantic googling, but\nif I happen to find them again I will add them here:\n\n- https://www.avanderlee.com/swift/diffable-data-sources-core-data/\n- https://developer.apple.com/videos/play/wwdc2019/202/\n- https://developer.apple.com/documentation/coredata/synchronizing_a_local_store_to_the_cloud\n- https://stackoverflow.com/questions/57304922/crash-when-adopting-nssecureunarchivefromdatatransformer-for-a-transformable-pro\n\n# You Made It!\n\nLet me know what you think - [@jazzychad](https://twitter.com/jazzychad) on twitter.\n\nAnd don't forget to check out [Sticker Doodle!](https://stickerdoodle.app)\n","funding_links":[],"categories":["Sample"],"sub_categories":["RSS"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjazzychad%2FiCloudCoreDataStarter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjazzychad%2FiCloudCoreDataStarter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjazzychad%2FiCloudCoreDataStarter/lists"}