{"id":21656704,"url":"https://github.com/enhorn/onecloudykitty","last_synced_at":"2026-02-06T18:33:05.552Z","repository":{"id":263828549,"uuid":"886542235","full_name":"enhorn/OneCloudyKitty","owner":"enhorn","description":"A small package to simplify basic CloudKit interactions.","archived":false,"fork":false,"pushed_at":"2025-01-05T07:04:17.000Z","size":76,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-20T05:17:18.313Z","etag":null,"topics":["cloudkit","ios","macos","swift"],"latest_commit_sha":null,"homepage":"","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/enhorn.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-11-11T06:53:58.000Z","updated_at":"2025-01-12T10:38:37.000Z","dependencies_parsed_at":"2024-11-20T14:38:04.256Z","dependency_job_id":null,"html_url":"https://github.com/enhorn/OneCloudyKitty","commit_stats":null,"previous_names":["enhorn/onecloudykitty"],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/enhorn%2FOneCloudyKitty","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/enhorn%2FOneCloudyKitty/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/enhorn%2FOneCloudyKitty/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/enhorn%2FOneCloudyKitty/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/enhorn","download_url":"https://codeload.github.com/enhorn/OneCloudyKitty/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244554125,"owners_count":20471173,"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":["cloudkit","ios","macos","swift"],"created_at":"2024-11-25T09:16:19.882Z","updated_at":"2026-02-06T18:33:05.519Z","avatar_url":"https://github.com/enhorn.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# OneCloudyKitty\nA small package to simplify basic CloudKit interactions, uses Async/Await and pulls data on a configured time interval.\n\n## Usage example\n\nThe content this tutorial, with some extras, can be found in the package's Example project.\n\nBefore we start set up a CloudKit container and selecting it in your Xcode project. This README doesn't cover that generic CloudKit part.\n\nWe start by defining our model that we want to store.\n```swift\n// Model fulfilling the protocol `OneRecordable`.\nfinal class SomeEntity: OneRecordable {\n\n    var recordID: CKRecord.ID // Required by `OneRecordable`.\n    var name: String\n    var age: Int\n\n    init(name: String, age: Int) {\n        self.recordID = Self.generateID() // Has a default implementation in `OneRecordable`.\n        self.name = name\n        self.age = age\n    }\n\n    // Required by `OneRecordable`. Can return `nil`.\n    required init?(_ record: CKRecord) {\n        guard let name = record[\"name\"] as? String, let age = record[\"age\"] as? Int else { return nil }\n        self.recordID = record.recordID\n        self.name = name\n        self.age = age\n    }\n\n}\n```\n\nWe can then use the model together with an `OneCloudController`.\n```swift\nlet controller = OneCloudController(database: .public, containerID: \"iCloud.SomeTestContainer\")\n\n// Interactions with CloudKit will throw when failing.\n// So for this example we put everything in a single do/catch.\ndo {\n\n    // The entity will now be saved to CloudKit.\n    let entity = try await controller.create(entity: SomeEntity(name: \"Some entity\", age: 41))\n\n    // Here we update one of the properties and save the entity.\n    // Saving, updating and deleting entities return a `discardable` copy of the entity.\n    entity.name = \"Saved entity\"\n    entity = try await controller.save(entity: entity)\n\n    // We can update a single property based on a keypath.\n    entity = try await controller.updateProperty(entity: entity, property: \\.age, value: 42)\n\n    // We can fetch all entities stored in CloudKit.\n    // The `getAll()` function has an optional `NSPredicate` parameter for filtering the result.\n    let entities: [SomeEntity] = try await controller.getAll()\n\n    // And we can delete the entity from the CloudKit storage.\n    try await controller.delete(entity: entity)\n\n} catch let error {\n    print(error)\n}\n```\n\nLet's define a list that automatically updates it's content based on a subscribed entity type.\n```swift\nstruct EntitiesList: View {\n\n    // The subscriber is `@Observable`.\n    let subscriber: OneSubscriber\u003cSomeEntity\u003e\n\n    var body: some View {\n        List {\n            // The observable subscriber has a published array named `entities`.\n            ForEach(subscriber.entities) { entity in\n                Text(entity.name)\n            }\n        }.refreshable {\n            do {\n                // We can manually trigger a refresh of the entities.\n                try await subscriber.refresh()\n            } catch let error {\n                print(error)\n            }\n        }\n    }\n\n}\n```\n\nAnd now let's display the list in our app.\n```swift\nstruct ContentView: View {\n\n    let controller = OneCloudController(database: .public, containerID: \"iCloud.SomeTestContainer\")\n    let subscriber: OneSubscriber\u003cSomeEntity\u003e\n\n    init () {\n        // Takes an optional `NSPredicate` parameter for filtering the fetched entities.\n        // Also has an optional time interval for pulling. Defaults to `5` minutes.\n        subscriber = OneSubscriber\u003cSomeEntity\u003e(controller: controller)\n        subscriber.start() // Starts pulling data\n    }\n\n    var body: some View {\n        NavigationStack {\n            EntitiesList(subscriber: subscriber)\n                .navigationTitle(\"My subscribed entities\")\n        }\n    }\n\n}\n```\n\nThere is also a database backed subscriber available, that will keep the database up-to-date with CloudKit.\n```swift\nlet subscriber = try OneStoredSubscriber\u003cSomeEntity.StorageModel, SomeEntity\u003e(\n    containerURL: URL.applicationSupportDirectory.appendingPathComponent(\"MyApp/database.sqlite\"),\n    controller: OneCloudController(containerID: \"iCloud.SomeTestContainer\"),\n    updateModel: { model, entity in\n        model.name = entity.name\n        model.age = entity.age\n        model.changeDate = entity.changeDate\n    },\n    updateEntity: { entity, model in\n        entity.name = model.name\n        entity.age = model.age\n        entity.changeDate = model.changeDate\n    }\n)\n```\n\nAnd a generic reusable stored subscriber as well, where we have typed subscripts in the entity for supported stored types.\nThe entities will always be stored as `OneGenericEntity` in CoudKit, and `OneGenericStorageModel` in the database.\n```swift\nlet subscriber = try OneGenericStoredSubscriber.genericStoredSubscriber(\n    containerURL: URL.applicationSupportDirectory.appendingPathComponent(\"MyApp/database.sqlite\"),\n    controller: OneCloudController(containerID: \"iCloud.SomeTestContainer\")\n)\n\nif let entity = subscriber.entities.first {\n    entity[string: \"value-key\"] // Optional String value\n    entity[integer: \"value-key\"] // Optional Int value\n    entity[double: \"value-key\"] // Optional Double value\n    entity[date: \"value-key\"] // Optional Date value\n    entity[data: \"value-key\"] // Optional Data value\n    entity[bool: \"value-key\", defaultValue: false] // Bool value, `defaultValue` is optionl.\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fenhorn%2Fonecloudykitty","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fenhorn%2Fonecloudykitty","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fenhorn%2Fonecloudykitty/lists"}