SwiftyCloudKit is a thin layer above Cloud Kit which makes it easy to implement cloud support into iOS apps.
To run the example project, clone the repo, and run pod install
from the Example directory. It is strongly recommended to run through the tutorial with the example project.
- Swift 4.2 (use pre 0.1.5 for Swift 4.0)
- iOS: 10.0+
SwiftyCloudKit is available through CocoaPods. To install it, simply add the following line to your Podfile:
pod 'SwiftyCloudKit'
SwiftyCloudKit is structured into three submodules: CloudKitFetcher, CloudKitHandler and CloudKitSubscriber. Note: The library supports offline capabilities. If offlineSupport
is set to true, and there is a case where an internet connection is not present, the library will store records temporarily locally, and upload them later.
The CloudKitFetcher fetches records from iCloud. Remember to set up a record type at the CloudKit Dashboard first. The protocol requires you to implement four variables, which define how the fetch is executed (read more about these in the documentation or in the example project).
var database: CKDatabase
var query: CKQuery?
var interval: Int
var cursor: CKQueryOperation.Cursor?
var zoneID: CKRecordZone.ID
var desiredKeys: [String]?
Then simply call the fetch function in e.g. viewDidAppear to fetch the records:
fetch(withCompletionHandler: { (records, error) in
// Do something with the fetched records.
})
CloudKitHandler allows you to upload and delete mulitple CloudKit records in a single operation. You can specify a priority for the operation and retrieve callbacks on the prorgress for the operation for each record. If an upload or deletion operation fails because of an iCloud error, the error included in the completion handler will be a CKError. If the operation fails because the library didn't detect an internet connection and failed to save or delete locally, it will return a LocalStorageError.
upload(records: [CKRecord], withPriority priority: QualityOfService, perRecordProgress: ((CKRecord, Double) -> Void)?, andCompletionHandler completionHandler: (([CKRecord]?, Error?) -> Void)?)
func delete(records: [CKRecord], withPriority priority: QualityOfService, perRecordProgress: ((CKRecord, Double) -> Void)?, andCompletionHandler completionHandler: (([CKRecord.ID]?, Error?) -> Void)?)
An example:
let record = CKRecord(recordType: MyRecordType)
record.set(string: "Hello World", key: MyStringKey)
upload(records: [record], withPriority: .userInitiated, perRecordProgress: nil) { (uploadedRecords, error) in
// Do something with the uploaded record
})
delete(records: [record], withPriority: .userInitiated, perRecordProgress: nil) { (deletedRecordIDs, error) in
// Do something when the record is deleted
})
There exist helper functions for every type supported by CloudKit. So you can retrieve and set strings, references, data, assets, ints, doubles, locations, dates, lists of the these types, as well as images and videos using the helper functions. If you store a image in the record you'll retrieve an optional UIImage when asking for the image, for a video you'll receive an optional URL to a temporary local file which can be used in an AVPlayer. In that way you don't have to deal with conversion. Note: As the videos are stored locally as cache, it's necessary to clear the cache from time to time. Call deleteLocalVideos()
in e.g. applicationWillTerminate(_ application: UIApplication)
in the AppDelegate to remove them. If you want to remove certain videos, use FileManager (the videos are stored in the documents folder with the filename template video_recordName_key.mov)
To retrieve values, use value(_ key: String)
. E.g.:
let myString = record.string(MyStringKey)
In order to set values, use set(value: Value, key: String)
. E.g:
record.set(string: "Hello World", key: MyStringKey)
A subscription is useful when there are multiple units having read and write access to the same data. An example would be an app which allows multiple users to collaborate on a spreadsheet. The subscription fires a notification when new data is appended, when data is deleted and when data is modified. The example project includes a demo concerning this (be aware that the iOS simulator can't send these notifications, only receive, so test between two iOS devices).
The prerequisites for subscriptions are:
- Adding remote-notification to UIBackgroundModes in info.plist
- Register for remote notifications in the app delegate and post notifications around the app when a push notification is received, which is done the following way:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
application.registerForRemoteNotifications()
return true
}
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
let ckqn = CKQueryNotification(fromRemoteNotificationDictionary: userInfo as! [String:NSObject])
let notification = Notification(name: NSNotification.Name(rawValue: CloudKitNotifications.NotificationReceived),
object: self,
userInfo: [CloudKitNotifications.NotificationKey: ckqn])
NotificationCenter.default.post(notification)
}
The next step is to conform to the protocol:
var subscription: CKQuerySubscription
func handleSubscriptionNotification(ckqn: CKQueryNotification) {}
And the final step is to subscribe to and unsubscribe from updates. This is necessary as subscriptions are quite expensive:
// Call in e.g. viewDidAppear
subscribe(_ completionHandler: ((CKError?) -> Void)?)
// Call in e.g. viewDidDisappear
unsubscribe(_ completionHandler: ((CKError?) -> Void)?)
The library includes helper functions to make it easy let users manage their data.
retrieveRecords(containerRecordTypes: [CKContainer: [String]]) -> [CKContainer: [CKRecord]]
The following methods erases all private and public data created by the user in the given containers.
erasePrivateData(inContainers containers: [CKContainer], completionHandler: @escaping (Error?) -> Void)
eraseUserCreatedPublicData(containerRecordTypes: [CKContainer: [String]], completionHandler: @escaping (Error?) -> Void)
In order to restrict databases and lift restrictions, use the following methods:
restrict(container: CKContainer, apiToken: String, webToken: String, environment: Environment, completionHandler: @escaping (Error?) -> Void)
unrestrict(container: CKContainer, apiToken: String, webToken: String, environment: Environment, completionHandler: @escaping (Error?) -> Void)
Reusable API tokens (created in CloudKit Dashboard) and web tokens are required for the requests to qualify. Create web tokens using:
restrictTokens(forContainersWithAPITokens containerTokens: [CKContainer: String]) -> [CKContainer:String]
Simen Gangstad, [email protected]
SwiftyCloudKit is available under the MIT license. See the LICENSE file for more info.