https://github.com/enhorn/onecloudykitty
A small package to simplify basic CloudKit interactions.
https://github.com/enhorn/onecloudykitty
cloudkit ios macos swift
Last synced: 3 months ago
JSON representation
A small package to simplify basic CloudKit interactions.
- Host: GitHub
- URL: https://github.com/enhorn/onecloudykitty
- Owner: enhorn
- License: mit
- Created: 2024-11-11T06:53:58.000Z (over 1 year ago)
- Default Branch: main
- Last Pushed: 2025-01-05T07:04:17.000Z (over 1 year ago)
- Last Synced: 2025-03-20T05:17:18.313Z (about 1 year ago)
- Topics: cloudkit, ios, macos, swift
- Language: Swift
- Homepage:
- Size: 74.2 KB
- Stars: 2
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# OneCloudyKitty
A small package to simplify basic CloudKit interactions, uses Async/Await and pulls data on a configured time interval.
## Usage example
The content this tutorial, with some extras, can be found in the package's Example project.
Before we start set up a CloudKit container and selecting it in your Xcode project. This README doesn't cover that generic CloudKit part.
We start by defining our model that we want to store.
```swift
// Model fulfilling the protocol `OneRecordable`.
final class SomeEntity: OneRecordable {
var recordID: CKRecord.ID // Required by `OneRecordable`.
var name: String
var age: Int
init(name: String, age: Int) {
self.recordID = Self.generateID() // Has a default implementation in `OneRecordable`.
self.name = name
self.age = age
}
// Required by `OneRecordable`. Can return `nil`.
required init?(_ record: CKRecord) {
guard let name = record["name"] as? String, let age = record["age"] as? Int else { return nil }
self.recordID = record.recordID
self.name = name
self.age = age
}
}
```
We can then use the model together with an `OneCloudController`.
```swift
let controller = OneCloudController(database: .public, containerID: "iCloud.SomeTestContainer")
// Interactions with CloudKit will throw when failing.
// So for this example we put everything in a single do/catch.
do {
// The entity will now be saved to CloudKit.
let entity = try await controller.create(entity: SomeEntity(name: "Some entity", age: 41))
// Here we update one of the properties and save the entity.
// Saving, updating and deleting entities return a `discardable` copy of the entity.
entity.name = "Saved entity"
entity = try await controller.save(entity: entity)
// We can update a single property based on a keypath.
entity = try await controller.updateProperty(entity: entity, property: \.age, value: 42)
// We can fetch all entities stored in CloudKit.
// The `getAll()` function has an optional `NSPredicate` parameter for filtering the result.
let entities: [SomeEntity] = try await controller.getAll()
// And we can delete the entity from the CloudKit storage.
try await controller.delete(entity: entity)
} catch let error {
print(error)
}
```
Let's define a list that automatically updates it's content based on a subscribed entity type.
```swift
struct EntitiesList: View {
// The subscriber is `@Observable`.
let subscriber: OneSubscriber
var body: some View {
List {
// The observable subscriber has a published array named `entities`.
ForEach(subscriber.entities) { entity in
Text(entity.name)
}
}.refreshable {
do {
// We can manually trigger a refresh of the entities.
try await subscriber.refresh()
} catch let error {
print(error)
}
}
}
}
```
And now let's display the list in our app.
```swift
struct ContentView: View {
let controller = OneCloudController(database: .public, containerID: "iCloud.SomeTestContainer")
let subscriber: OneSubscriber
init () {
// Takes an optional `NSPredicate` parameter for filtering the fetched entities.
// Also has an optional time interval for pulling. Defaults to `5` minutes.
subscriber = OneSubscriber(controller: controller)
subscriber.start() // Starts pulling data
}
var body: some View {
NavigationStack {
EntitiesList(subscriber: subscriber)
.navigationTitle("My subscribed entities")
}
}
}
```
There is also a database backed subscriber available, that will keep the database up-to-date with CloudKit.
```swift
let subscriber = try OneStoredSubscriber(
containerURL: URL.applicationSupportDirectory.appendingPathComponent("MyApp/database.sqlite"),
controller: OneCloudController(containerID: "iCloud.SomeTestContainer"),
updateModel: { model, entity in
model.name = entity.name
model.age = entity.age
model.changeDate = entity.changeDate
},
updateEntity: { entity, model in
entity.name = model.name
entity.age = model.age
entity.changeDate = model.changeDate
}
)
```
And a generic reusable stored subscriber as well, where we have typed subscripts in the entity for supported stored types.
The entities will always be stored as `OneGenericEntity` in CoudKit, and `OneGenericStorageModel` in the database.
```swift
let subscriber = try OneGenericStoredSubscriber.genericStoredSubscriber(
containerURL: URL.applicationSupportDirectory.appendingPathComponent("MyApp/database.sqlite"),
controller: OneCloudController(containerID: "iCloud.SomeTestContainer")
)
if let entity = subscriber.entities.first {
entity[string: "value-key"] // Optional String value
entity[integer: "value-key"] // Optional Int value
entity[double: "value-key"] // Optional Double value
entity[date: "value-key"] // Optional Date value
entity[data: "value-key"] // Optional Data value
entity[bool: "value-key", defaultValue: false] // Bool value, `defaultValue` is optionl.
}
```