Reputation: 2488
I have a simple iOS app written in Swift that uses CloudKit to save a string to the cloud. The user can update the string at anytime (overwriting the original string). This works fine in my code. I want the app to auto update if the user changes the string on a different device (say they make a change to the string on their iPad, I want their iPhone to auto update). I tried to find a basic CloudKit subscription tutorial but all of them deal with more advanced implementations. How can I implement a simple CloudKit subscription? My code is as follows:
import UIKit
import CloudKit
class ViewController: UIViewController {
// This the text field where the user inputs their string to save to the cloud
@IBOutlet weak var userInput1: UITextField!
// Save button
// When the user taps save, their string is saved to the cloud
@IBAction func saveButton(sender: AnyObject) {
let container = CKContainer.defaultContainer()
let privateDatabase = container.privateCloudDatabase
let myRecordName = "1001"
let recordID = CKRecordID(recordName: myRecordName)
privateDatabase.fetchRecordWithID(recordID, completionHandler: { (record, error) in
if error != nil {
} else {
record!.setObject(self.userInput1.text, forKey: "userInput1")
privateDatabase.saveRecord(record!, completionHandler: { (savedRecord, saveError) in
if saveError != nil {
print("Error saving record.")
} else {
print("Successfully updated record!")
}
})
}
})
}
override func viewDidLoad() {
super.viewDidLoad()
// Auto populates app with cloud string
let container = CKContainer.defaultContainer()
let privateDatabase = container.privateCloudDatabase
let myRecordName = "1001"
let recordID = CKRecordID(recordName: myRecordName)
privateDatabase.fetchRecordWithID(recordID, completionHandler: { (record, error) in
if error != nil {
// Nothing saved to cloud yet
print(error)
} else {
// Display record
dispatch_async(dispatch_get_main_queue()) {
self.userInput1.text = (record!["userInput1"]! as! String)
}
}
})
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
Upvotes: 2
Views: 1821
Reputation: 828
You could use CloudKit subscriptions to save this string, however if you are just looking for a simple way to save a string that is persisted across a single iCloud user's devices I would definitely recommend just using NSUbiquitousKeyValueStore
, which works like NSUserDefaults
but across all of an iCloud user's devices. To turn on this store just go to the "Capabilities" section of your project settings. Enable iCloud and check the "Key-value storage" box.
In code using this is as simple as:
NSUbiquitousKeyValueStore.defaultStore().setObject("the user's string", forKey: "userString")
and
let userString = NSUbiquitousKeyValueStore.defaultStore().objectForKey("userString") as? String
If you would like to get realtime notifications for when the key value store changes, just listen for the NSUbiquitousKeyValueStoreDidChangeExternallyNotification
Upvotes: 1
Reputation: 8995
There are two parts to a subscription; the first the saving the subscription, the second the code to fire when it does. This code creates a subscription with its own CKRecordID, is a tad more complex than it needs to be. It also fires on updates and deletions, there is a third option. Creation. And yes its in the public database, your currently in the private. So you got some changes to make, a challenge.
func subscribe4Cloud() {
let container = CKContainer(identifier: "iCloud.com")
let publicDB = container.publicCloudDatabase
let predicateY = NSPredicate(value: true)
let query = CKQuery(recordType: "Collections", predicate: predicateY)
publicDB.performQuery(query, inZoneWithID: nil) {
records, error in
if error != nil {
print("twang this is broken \(error)")
} else {
if records!.count > 0 {
subID = String(NSUUID().UUIDString)
let predicateY = NSPredicate(value: true)
let subscription = CKSubscription(recordType: "Collections", predicate: predicateY, subscriptionID: subID, options: [.FiresOnRecordUpdate, .FiresOnRecordDeletion])
subscription.notificationInfo = CKNotificationInfo()
publicDB.saveSubscription(subscription) {
subscription, error in
if error != nil {
print("ping sub failed, almost certainly cause it is already there \(theLink) \(error)")
} else {
print("bing subscription saved! \(subID) ")
}
}
} else {
print("no records found")
}
}
}
}
This is the first part. Cavet: A key point you may miss [I did]. Once you've saved the subscription; you don't need to do it again; you need to run this only once.
You can also do this in the CloudKit dashboard. The second part is to setup your app delegate to pickup the subscription, which you do by adding this method.
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: () -> Void) {
if application.applicationState == .Inactive {
// Do nothing
}
let notification = CKQueryNotification(fromRemoteNotificationDictionary: userInfo as! [String : NSObject])
let container = CKContainer(identifier: "iCloud.com")
let publicDB = container.publicCloudDatabase
if notification.notificationType == .Query {
let queryNotification = notification as! CKQueryNotification
if queryNotification.queryNotificationReason == .RecordUpdated {
print("queryNotification.recordID \(queryNotification.recordID)")
}
}
print("userInfo \(userInfo)")
}
Again there maybe a few lines of code here that is not strictly needed; I cut'n'pasted this from a project I worked on.
This code will start you off; I make little to no effort to look at notification send thru, beyond printing it. You need to parse it and look at the bit your interested in.
Your third challenge to implement KVO or NSNotifications or even a delegate to get the message from here to other classes in your code; so from the app delegate to your view controller in your case!
Working with cloudKit isn't easy, it a good place to try and learn to code, it can be a real challenge and a great way learn some very important coding principles, practices and techniques.
Upvotes: 2