{"id":13995528,"url":"https://github.com/paulw11/Seam3","last_synced_at":"2025-07-22T22:31:10.463Z","repository":{"id":54687843,"uuid":"75159905","full_name":"paulw11/Seam3","owner":"paulw11","description":"Cloudkit based persistent store for Core Data","archived":false,"fork":false,"pushed_at":"2021-02-03T22:20:11.000Z","size":7409,"stargazers_count":211,"open_issues_count":17,"forks_count":24,"subscribers_count":13,"default_branch":"master","last_synced_at":"2025-07-13T09:26:24.430Z","etag":null,"topics":["cloudkit","coredata-model","coredata-store","swift","sync"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/paulw11.png","metadata":{"files":{"readme":"README.md","changelog":"changelog.md","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":"2016-11-30T06:52:27.000Z","updated_at":"2025-03-17T14:32:11.000Z","dependencies_parsed_at":"2022-08-14T00:01:02.942Z","dependency_job_id":null,"html_url":"https://github.com/paulw11/Seam3","commit_stats":null,"previous_names":[],"tags_count":41,"template":false,"template_full_name":null,"purl":"pkg:github/paulw11/Seam3","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/paulw11%2FSeam3","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/paulw11%2FSeam3/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/paulw11%2FSeam3/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/paulw11%2FSeam3/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/paulw11","download_url":"https://codeload.github.com/paulw11/Seam3/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/paulw11%2FSeam3/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266585687,"owners_count":23952163,"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","status":"online","status_checked_at":"2025-07-22T02:00:09.085Z","response_time":66,"last_error":null,"robots_txt_status":null,"robots_txt_updated_at":null,"robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["cloudkit","coredata-model","coredata-store","swift","sync"],"created_at":"2024-08-09T14:03:27.930Z","updated_at":"2025-07-22T22:31:09.767Z","avatar_url":"https://github.com/paulw11.png","language":"Swift","funding_links":[],"categories":["Swift"],"sub_categories":[],"readme":"# Seam3\n\n[![CI Status](http://img.shields.io/travis/paulw11/Seam3.svg?style=flat)](https://travis-ci.org/paulw11/Seam3)\n[![Version](https://img.shields.io/cocoapods/v/Seam3.svg?style=flat)](http://cocoapods.org/pods/Seam3)\n[![License](https://img.shields.io/cocoapods/l/Seam3.svg?style=flat)](http://cocoapods.org/pods/Seam3)\n[![Platform](https://img.shields.io/cocoapods/p/Seam3.svg?style=flat)](http://cocoapods.org/pods/Seam3)\n[![GitHub stars](https://img.shields.io/github/stars/paulw11/Seam3.svg)](https://github.com/paulw11/Seam3/stargazers)\n[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)\n\nSeam3 is a framework built to bridge gaps between CoreData and CloudKit. It almost handles all the CloudKit hassle. \nAll you have to do is use it as a store type for your CoreData store. \nLocal caching and sync is taken care of. \n\nSeam3 is based on [Seam](https://github.com/nofelmahmood/Seam) by [nofelmahmood](https://github.com/nofelmahmood)\n\nChanges in Seam3 include:\n\n* Corrects one-to-many and many-to-one relationship mapping between CoreData and CloudKit\n* Adds mapping between binary attributes in CoreData and CKAssets in CloudKit\n* Code updates for Swift 3.0 on iOS 10, Mac OS 10.11 \u0026 tvOS 10\n* Restructures code to eliminate the use of global variables\n\n## CoreData to CloudKit\n\n### Attributes\n\n| CoreData  | CloudKit |\n| ------------- | ------------- |\n| NSDate    | Date/Time\n| Binary Data | Bytes or CKAsset (See below) |\n| NSString  | String   |\n| Integer16 | Int(64) |\n| Integer32 | Int(64) |\n| Integer64 | Int(64) |\n| Decimal | Double | \n| Float | Double |\n| Boolean | Int(64) |\n| NSManagedObject | Reference |\n\n**In the table above :** `Integer16`, `Integer32`, `Integer64`, `Decimal`, `Float` and `Boolean` are referring to the instance of `NSNumber` used \nto represent them in CoreData Models. `NSManagedObject` refers to a `to-one relationship` in a CoreData Model.\n\nIf a `Binary Data` attribute has the *Allows External Storage* option selected, it will be stored as a `CKAsset` in Cloud Kit, otherwise it will be stored as `Bytes` in the `CKRecord` itself.\n\n### Relationships\n\n| CoreData Relationship  | Translation on CloudKit |\n| ------------- | ------------- |\n| To - one    | To one relationships are translated as CKReferences on the CloudKit Servers.|\n| To - many    | To many relationships are not explicitly created. Seam3 only creates and manages to-one relationships on the CloudKit Servers. \u003cbr/\u003e \u003cstrong\u003eExample\u003c/strong\u003e -\u003e If an Employee has a to-one relationship to Department and Department has a to-many relationship to Employee than Seam3 will only create the former on the CloudKit Servers. It will fullfil the later by using the to-one relationship. If all employees of a department are accessed Seam3 will fulfil it by fetching all the employees that belong to that particular department.|\n\n\u003cstrong\u003eNote :\u003c/strong\u003e You must create inverse relationships in your app's CoreData Model or Seam3 wouldn't be able to translate CoreData Models in to CloudKit Records. Unexpected errors and corruption of data can possibly occur.\n\n## Sync\n\nSeam3 keeps the CoreData store in sync with the CloudKit Servers. It let's you know when the sync operation starts and finishes by throwing the following two notifications.\n- `smSyncDidStart` `Notification`\n- `smSyncDidFinish` `Notification`\n\nIf an error occurred during the sync operation, then the `userInfo` property of the `smSyncDidFinish` notification will contain an `Error` object for the key `SMStore.SMStoreErrorDomain`\n\n#### Conflict Resolution Policies\nIn case of any sync conflicts, Seam3 exposes 3 conflict resolution policies.\n\n- `clientTellsWhichWins`\n\nThis policy requires you to set the `syncConflictResolutionBlock` property of your `SMStore`. The closure you specify will receive three `CKRecord` arguments; The first is the current server record.  The second is the current client record and the third is the client record before the most recent change.  Your closure must modify and return the server record that was passed as the first argument.\n\n- `serverRecordWins`\n\nThis is the default. It considers the server record as the true record.\n\n- `clientRecordWins`\n\nThis considers the client record as the true record.\n\n## How to use\n\n- Declare a SMStore type property in the class where your CoreData stack resides.\n```swift\nvar smStore: SMStore?\n```\n- For iOS9 and earlier or macOS, add a store type of `SMStore.type` to your app's NSPersistentStoreCoordinator and assign it to the property created in the previous step.\n```swift\n\nSMStore.registerStoreClass()\ndo \n{\n   self.smStore = try coordinator.addPersistentStoreWithType(SMStore.type, configuration: nil, URL: url, options: nil) as? SMStore\n}\n```\n- For iOS10 using `NSPersistentContainer`:\n\n```swift\nlazy var persistentContainer: NSPersistentContainer = {\n\n        SMStore.registerStoreClass()\n\n        let container = NSPersistentContainer(name: \"Seam3Demo2\")\n        \n        let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)\n        \n        if let applicationDocumentsDirectory = urls.last {\n            \n            let url = applicationDocumentsDirectory.appendingPathComponent(\"SingleViewCoreData.sqlite\")\n            \n            let storeDescription = NSPersistentStoreDescription(url: url)\n            \n            storeDescription.type = SMStore.type\n            \n            container.persistentStoreDescriptions=[storeDescription]\n            \n            container.loadPersistentStores(completionHandler: { (storeDescription, error) in\n                if let error = error as NSError? {\n                    // Replace this implementation with code to handle the error appropriately.\n                    // 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.\n                    \n                    /*\n                     Typical reasons for an error here include:\n                     * The parent directory does not exist, cannot be created, or disallows writing.\n                     * The persistent store is not accessible, due to permissions or data protection when the device is locked.\n                     * The device is out of space.\n                     * The store could not be migrated to the current model version.\n                     Check the error message to determine what the actual problem was.\n                     */\n                    fatalError(\"Unresolved error \\(error), \\(error.userInfo)\")\n                }\n            })\n            return container\n        }\n        \n        fatalError(\"Unable to access documents directory\")\n        \n    }()\n```\nBy default, logs will be written to `os_log`, but you can route log messages to your own class by extending `SMLogger`:\n```swift\nclass AppDelegate: SMLogDelegate {\n    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -\u003e Bool {\n        SMStore.logger = self\n    }\n    // MARK: SMLogDelegate\n    func log(_ message: @autoclosure() -\u003e String, type: SMLogType) {\n        #if DEBUG\n        switch type {\n        case .debug:\n        print(\"Debug: \\(message())\")\n        case .info:\n        print(\"Info: \\(message())\")\n        case .error:\n        print(\"Error: \\(message())\")\n        case .fault:\n        print(\"Fault: \\(message())\")\n        case .defaultType:\n        print(\"Default: \\(message())\")\n        }\n        #endif\n    }\n}\n```\nYou can access the `SMStore` instance using:\n```\nself.smStore = container.persistentStoreCoordinator.persistentStores.first as? SMStore\n```\nBefore triggering a sync, you should check the Cloud Kit authentication status and check for a changed Cloud Kit user:\n```\nself.smStore?.verifyCloudKitConnectionAndUser() { (status, user, error) in\n    guard status == .available, error == nil else {\n        NSLog(\"Unable to verify CloudKit Connection \\(error)\")\n        return  \n    } \n\n    guard let currentUser = user else {\n        NSLog(\"No current CloudKit user\")\n        return\n    }\n\n    var completeSync = false\n\n    let previousUser = UserDefaults.standard.string(forKey: \"CloudKitUser\")\n    if  previousUser != currentUser {\n        do {\n            print(\"New user\")\n            try self.smStore?.resetBackingStore()\n            completeSync = true\n        } catch {\n            NSLog(\"Error resetting backing store - \\(error.localizedDescription)\")\n            return\n        }\n    }\n\n    UserDefaults.standard.set(currentUser, forKey:\"CloudKitUser\")\n\n    self.smStore?.triggerSync(complete: completeSync)\n}\n```\n- Enable Push Notifications for your app.\n![](http://s29.postimg.org/rb9vj0egn/Screen_Shot_2015_08_23_at_5_44_59_pm.png)\n- Implement didReceiveRemoteNotification Method in your AppDelegate and call `handlePush` on the instance of SMStore created earlier.\n```swift\nfunc application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -\u003e Void) {\n        print(\"Received push\")\n\n        smStore?.handlePush(userInfo: userInfo) { (result) in\n            completionHandler(result.uiBackgroundFetchResult)\n        }\n    }\n```\n- Enjoy\n\n## Cross-platform considerations\nThe default Cloud Kit container is named using your app or application's *bundle identifier*.  If you want to share Cloud Kit data between apps on different platforms (e.g. iOS and macOS) then you need to use a named Cloud Kit container.  You can specify a cloud kit container when you create your SMStore instance.\n\nOn iOS10, specify the `SMStore.SMStoreContainerOption` using the `NSPersistentStoreDescription` object\n\n```\nlet storeDescription = NSPersistentStoreDescription(url: url)\nstoreDescription.type = SMStore.type\nstoreDescription.setOption(\"iCloud.org.cocoapods.demo.Seam3-Example\" as NSString, forKey: SMStore.SMStoreContainerOption)\n```\n\nOn iOS9 and macOS specify an options dictionary to the persistent store coordinator\n\n```\nlet options:[String:Any] = [SMStore.SMStoreContainerOption:\"iCloud.org.cocoapods.demo.Seam3-Example\"]\nself.smStore = try coordinator!.addPersistentStore(ofType: SMStore.type, configurationName: nil, at: url, options: options) as? SMStore\n```\nEnsure that you specify the value you specify is selected under *iCloud containers* on the *capabilities* tab for your app in Xcode.\n\n## Migrating from Seam to Seam3\n\nMigration should be quite straight-forward, as the format used to store data in CloudKit and in the local backing store haven't changed.\nChange the import statement to `import Seam3` and you should be good to go.\n\n## Example\n\nTo run the example project, clone the repo, and run `pod install` from the Example directory first.  If you are running on the simulator, make sure that you log in to iCloud using the settings app in the simulator.\n\n## Installation\n\nSeam3 is available through [CocoaPods](http://cocoapods.org). To install\nit, simply add the following line to your Podfile:\n\n```ruby\npod \"Seam3\"\n```\n\n## Author\n\npaulw, paulw@wilko.me\n\n## License\n\nSeam3 is available under the MIT license. See the LICENSE file for more info.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpaulw11%2FSeam3","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpaulw11%2FSeam3","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpaulw11%2FSeam3/lists"}