When considering using iCloud in an app, it is important to note that the Apple ecosystem is not limited to the iOS platform. It also encompasses a range of macOS-based laptop and desktop computer systems, all of which have access to iCloud services. This increases the chance that a user will have the same app in one form or another on several different devices and platforms. Take, for the sake of an example, a hypothetical news magazine app. A user may have this app installed on an iPhone and an iPad. If the user begins reading an article on the iPhone instance of the app and then switches to the same app on the iPad later, the iPad app should take the user to the position reached in the article on the iPhone so that the user can resume reading.
This kind of synchronization between apps is provided by the Key-Value data storage feature of iCloud. This chapter aims to provide an overview of this service and work through a very simple example of the feature in action in an iOS app.
An Overview of iCloud Key-Value Data Storage
The primary purpose of iCloud Key-Value data storage is to allow small amounts of data to be shared between instances of apps running on different devices or even different apps on the same device. The data may be synchronized if encapsulated in an array, dictionary, String, Date, Data, Boolean, or Number object.
iCloud data synchronization is achieved using the NSUbiquitousKeyValueStore class introduced as part of the iOS 5 SDK. Values are saved with a corresponding key using the set(forKey:) method. For example, the following code fragment creates an instance of an NSUbiquitousKeyValueStore object and then saves a string value using the key “MyString”:
var keyStore = NSUbiquitousKeyValueStore() keyStore.set("Saved String", forKey: "MyString")
Once key-value pairs have been saved locally, they will not be synchronized with iCloud storage until a call is made to the synchronize method of the NSUbiquitousKeyValueStore instance:
keyStore.synchronize()
It is important to note that a call to the synchronize method does not immediately synchronize the locally saved data with the iCloud store. Instead, iOS will synchronize at what the Apple documentation refers to as “an appropriate later time.”
A stored value may be retrieved by a call to the appropriate method corresponding to the data type to be retrieved (the format of which is <datatype>(forKey:)) and passing through the key as an argument. For example, the stored string in the above example may be retrieved as follows:
let storedString = keyStore.string(forKey: "MyString")
Sharing Data Between Apps
As with iCloud document storage, key-value data storage requires the implementation of appropriate iCloud entitlements. In this case, the app must have the com.apple.developer.ubiquity-kvstore-identifier entitlement key configured in the project’s entitlements file. The value assigned to this key identifies which apps can share access to the same iCloud-stored key-value data.
If, for example, the ubiquity-kvstore-identifier entitlement key for an app named MyApp is assigned a value of ABCDE12345.com.mycompany.MyApp (where ABCDEF12345 is the developer’s unique team or individual ID), then any other apps using the same entitlement value will also be able to access the same stored key-value data. This, by definition, will be any instance of the MyApp running on multiple devices but applies equally to entirely different apps (for example, MyOtherApp) if they also use the same entitlement value.
Data Storage Restrictions
iCloud key-value data storage is provided to meet the narrow requirement of performing essential synchronization between app instances, and the data storage limitations imposed by Apple reflect this.
The amount of data that can be stored per key-value pair is 1MB. The per-app key-value storage limit is 1024 individual keys which, combined, must also not exceed 1MB in total.
Conflict Resolution
If two app instances change the same key-value pair, the most recent change is given precedence.
Receiving Notification of Key-Value Changes
If two app instances change the same key-value pair, the most recent change is given precedence.
An app may register to be notified when another app instance changes stored values. This is achieved by setting up an observer on the NSUbiquitousKeyValueStoreDidChangeExternallyNotification notification. This notification is triggered when a change is made to any key-value pair in a specified key-value store and is passed an array of strings containing the keys that were changed together with an NSNumber indicating the reason for the change. If the available space for the key-value storage has been exceeded, this number will match the NSUbiquitousKeyValueStoreQuotaViolationChange constant value.
An iCloud Key-Value Data Storage Example
The remainder of this chapter is devoted to creating an app that uses iCloud key-value storage to store a key with a string value using iCloud. In addition to storing a key-value pair, the app will also configure an observer to receive a notification when another app instance changes the value.
Before starting on this project, it is important to note that membership to the Apple Developer Program will be required as outlined in How to Join the Apple Developer Program.
Begin the app creation process by launching Xcode and creating a new iOS App project named CloudKeys with Swift selected as the programming language.
Enabling the App for iCloud Key-Value Data Storage
A mandatory step in the app development is configuring the appropriate iCloud entitlement. Following the steps outlined in the Preparing an iOS 17 App to use iCloud Storage chapter, add the iCloud capability and enable the Key-value storage service:
Once selected, Xcode will create an entitlements file for the project named CloudKeys.entitlements containing the appropriate iCloud entitlements key-value pairs. Select the entitlements file from the project navigator and note the value assigned to the iCloud Key-Value Store key. By default, this is typically comprised of your team or individual developer ID combined with the app’s Bundle identifier. Any other apps that use the same value for the entitlement key will share access to the same iCloud-based key-value data stored by this app.
Designing the User Interface
The app will consist of a text field into which a string may be entered by the user and a button that, when selected, will save the string to the app’s iCloud key-value data store. First, select the Main.storyboard file, display the Library panel, and drag and drop the two objects onto the view canvas. Next, double-click on the button object and change the text to Store Key. The completed view should resemble Figure 45-2:
Click on the background View component in the layout, display the Resolve Auto Layout Issues menu, and select the Reset to Suggested Constraints option listed under All Views in the View Controller.
Select the text field object in the view canvas, display the Assistant Editor panel and verify that the editor is displaying the contents of the ViewController.swift file. Ctrl-click on the text field object and drag it to a position just below the class declaration line in the Assistant Editor. Release the line, and in the resulting connection dialog, establish an outlet connection named textField.
Finally, Ctrl-click on the button object and drag the line to the area immediately beneath the newly created outlet in the Assistant Editor panel. Release the line and, within the resulting connection dialog, establish an Action method on the Touch Up Inside event configured to call a method named saveKey.
Implementing the View Controller
In addition to the action and outlet references created above, an instance of the NSUbiquitousKeyStore class will be needed. Choose the ViewController.swift file, therefore, and modify it as follows:
class ViewController: UIViewController { var keyStore: NSUbiquitousKeyValueStore? @IBOutlet weak var textField: UITextField! . .
Modifying the viewDidLoad Method
The next step is to add a method to perform the initialization and call it from the viewDidLoad method of the view controller. Remaining within the ViewController.swift file, modify the code so that it reads as follows:
override func viewDidLoad() { super.viewDidLoad() initCloud() } func initCloud() { keyStore = NSUbiquitousKeyValueStore() let storedString = keyStore?.string(forKey: "MyString") if let stringValue = storedString { textField.text = stringValue } NotificationCenter.default.addObserver(self, selector: #selector( ViewController.ubiquitousKeyValueStoreDidChange), name: NSUbiquitousKeyValueStore.didChangeExternallyNotification, object: keyStore) }
The method begins by allocating and initializing an instance of the NSUbiquitousKeyValueStore class and assigning it to the keyStore variable. Next, the string(forKey:) method of the keyStore object is called to check if the MyString key is already in the key-value store. If the key exists, the string value is assigned to the text field object’s text property via the textField outlet.
Finally, the method sets up an observer to call the ubiquitousKeyValueStoreDidChange method when another app instance changes the stored key value.
Having implemented the code in the initCloud method, the next step is to write the ubiquitousKeyValueStoreDidChange method.
Implementing the Notification Method
Within the context of this example app, the ubiquitousKeyValueStoreDidChange method, triggered when another app instance modifies an iCloud-stored key-value pair, is provided to notify the user of the change via an alert message and to update the text in the text field with the new string value. The code for this method, which needs to be added to the ViewController.swift file, is as follows:
@objc func ubiquitousKeyValueStoreDidChange(notification: NSNotification) { let alert = UIAlertController(title: "Change detected", message: "iCloud key-value-store change detected", preferredStyle: UIAlertController.Style.alert) let cancelAction = UIAlertAction(title: "OK", style: .cancel, handler: nil) alert.addAction(cancelAction) self.present(alert, animated: true, completion: nil) textField.text = keyStore?.string(forKey: "MyString") }
Implementing the saveData Method
The final coding task involves the implementation of the saveData action method. This method will be called when the user touches the button in the user interface and needs to be implemented in the ViewController.swift file:
@IBAction func saveKey(_ sender: Any) { keyStore?.set(textField.text, forKey: "MyString") keyStore?.synchronize() }
The code for this method is quite simple. The set(forKey:) method of the keyStore object is called, assigning the current text property of the user interface textField object to the “MyString” key. The synchronize method of the keyStore object is then called to ensure that the key-value pair is synchronized with the iCloud store.
Testing the App
Click on the run button in the Xcode toolbar, and once the app is installed and running on the device or iOS Simulator, enter some text into the text field and tap the Store Key button. Next, stop the app from running by clicking on the stop button in the Xcode toolbar, then re-launch by clicking the run button. When the app reloads, the text field should be primed with the saved value string.
To test the change notification functionality, install the app on both a device and the iOS simulator. Then, with the app running on both, change the text on the iOS Simulator instance and save the key. After a short delay, the device-based instance of the app will detect the change, display the alert and update the text field to the new value.
Summary
iOS key-value data storage allows small amounts of data in the form of an array, dictionary, String, Date, Data, Boolean, or Number objects to be shared between instances of apps running on different devices running iOS and macOS. This chapter has outlined the steps in enabling and implementing this data sharing, including enabling key-value storage support and configuring a listener to detect changes.