user1822824
user1822824

Reputation: 2488

Implement a Basic CloudKit Subscription

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

Answers (2)

Marcus
Marcus

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

user3069232
user3069232

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

Related Questions