jazzedge
11/29/2017 - 8:34 PM

Swift - CloudKit - Configure App for Remote Notifications

See: http://www.techotopia.com/index.php/An_iOS_8_CloudKit_Subscription_Example

01. Configure App for Remote Notifications

Before adding any code to the project, a couple of configuration changes need to be made to the project to add support for notifications. First, the Info.plist file needs to be updated to indicate that the app supports remote notifications when in the background. To add this setting, select the Info.plist file from the project navigator panel and add a new entry for the Required background modes key.  

Once the key has been added, click on the right-facing arrow to the left of the key to unfold the values. In the value field for Item 0, enter remote-notification.

02. Registering an App to Receive Push Notifications

By default, applications installed on a user’s device will not be allowed to receive push notifications until the user specifically grants permission. An application seeks this permission by registering for remote notifications. The first time this registration request occurs the user will be prompted to grant permission. Once permission has been granted, the application will be able to receive remote notifications until the user changes the notifications setting for the application in the Settings app.To register for remote notifications for the CloudKitDemo project, locate and select the AppDelegate.swift file in the project navigator panel and modify the didFinishLaunchingWithOptions method to add the appropriate registration code. Now is also an opportune time to import the CloudKit and UserNotifications Frameworks into the class:

import UIKit
import CloudKit
import UserNotifications

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

        UNUserNotificationCenter.current().requestAuthorization(options:
            [[.alert, .sound, .badge]],
                completionHandler: { (granted, error) in
                    // Handle Error
            })
        application.registerForRemoteNotifications()

        return true
    }
.
.
}

The code in the method configures the notification settings such that the system will display an alert to the user when an notification is received, display a badge on the application’s launch icon and also, if configured, play a sound to get the user’s attention. Although sound is enabled for this example, only the alert and badge settings will be used.Having made these code changes, run the application and, when prompted, allow the application to receive notifications.

03.Handling Remote Notifications

When the user selects a notification alert on an iOS device, the CloudKitDemo application will be launched by the operating system. At the point that the user selects the notification, the application will currently be in one of three possible states – foreground, background or not currently running.
If the application is in the background when the alert is selected, it is simply brought to the foreground. If it was not currently running, the application is launched by the operating system and brought to the foreground. When the application is already in foreground or background state when a CloudKit notification alert is selected, the didReceiveRemoteNotification method of the application delegate class is called and passed as an argument an NSDictionary instance containing a CKNotification object which contains, among other information, the ID of the cloud database record that triggered the notification.If the application was not already in the foreground or background when the alert is selected, the didReceiveRemoteNotification method is not called. Instead, information about the database change is passed as an argument to the didFinishLaunchingWithOptions method of the application delegate.

Implementing the didReceiveRemoteNotification Method

The didReceiveRemoteNotification method will be called when the user selects an alert and the CloudKitDemo application is either already in the foreground or currently in the background. The method will need to be implemented in the AppDelegate.swift file so locate this file in the project navigator panel and modify it to add this method:


func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {

    let viewController: ViewController =
       self.window?.rootViewController as! ViewController

    let notification: CKNotification =
        CKNotification(fromRemoteNotificationDictionary:
            userInfo as! [String : NSObject])

    if (notification.notificationType ==
                CKNotificationType.query) {

        let queryNotification =
            notification as! CKQueryNotification

        let recordID = queryNotification.recordID

        viewController.fetchRecord(recordID!)
    }
}

The method begins by obtaining a reference to the root view controller of the application. The code then extracts the CKNotification object from the NSDictionary that was passed to the method by the operating system. The notificationType property of the CKNotification object is then checked to make sure it matches CKNotificationType.query (which indicates that the notification was triggered as a result of a subscription).
The record ID is then obtained and passed to the fetchRecord method on the view controller. 

04. Fetching a Record From a Cloud Database

The next step is to implement the fetchRecord method.

Records can be fetched by record ID from a cloud database using the fetch(withRecordID:) method of the cloud database object. Within the ViewController.swift file, implement the fetchRecord method as follows:

func fetchRecord(_ recordID: CKRecordID) -> Void
{
    privateDatabase?.fetch(withRecordID: recordID,
                     completionHandler: ({record, error in
        if let err = error {
            DispatchQueue.main.async() {
                self.notifyUser("Fetch Error", message:
                   err.localizedDescription)
            }
        } else {
            DispatchQueue.main.async() {
                self.currentRecord = record
                self.addressField.text =
                   record!.object(forKey: "address") as? String
                self.commentsField.text =
                   record!.object(forKey: "comment") as? String
                let photo =
                   record!.object(forKey: "photo") as! CKAsset

                let image = UIImage(contentsOfFile:
                   photo.fileURL.path)
                self.imageView.image = image
                self.photoURL = self.saveImageToFile(image!)
            }
        }
    }))
}
The code obtains a reference to the private cloud database (keep in mind that this code will be executed before the viewDidLoad method where this has previously been obtained) and then fetches the record from the cloud database based on the record ID passed through as a parameter. If the fetch operation is successful, the data and photo are extracted from the record and displayed to the user. Since the fetched record is also now the current record, it is stored in the currentRecord variable.

05. Completing the didFinishLaunchingWithOptions Method

As previously outlined, the didReceiveRemoteNotification method is only called when the user selected an alert notification and the application is already running either in the foreground or background. When the application was not already running, the didFinishLaunchingWithOptions method is called and passed information about the notification. In this scenario, it will be the responsibility of this method to ensure that the newly added record is displayed to the user when the application loads. Within the AppDelegate.swift file, locate the didFinishLaunchingWithOptions method and modify it as follows:

func application(application: UIApplication!, didFinishLaunchingWithOptions launchOptions: NSDictionary!) -> Bool {

    let settings = UIUserNotificationSettings(forTypes: 
        .Alert | .Badge | .Sound, categories: nil)

    application.registerUserNotificationSettings(settings)
    application.registerForRemoteNotifications()

    if let options: NSDictionary = launchOptions as NSDictionary? {
        let remoteNotification = 
		options[UIApplicationLaunchOptionsKey.remoteNotification]


        if let notification = remoteNotification {

            self.application(application, didReceiveRemoteNotification: 
			notification as! [AnyHashable : Any],
			 fetchCompletionHandler:  { (result) in
            })

        }
    }
    return true
}

The added source code begins by verifying that data has been passed to the method via the launchOptions parameter. The remote notification key is then used to obtain the NSDictionary object containing the notification data. If the key returns a value, it is passed to the didReceiveRemoteNotification method so that the record can be fetched and displayed to the user.

06. Testing the Application

Install and run the application on two devices or simulators (or a mixture thereof) remembering to log into iCloud on both instances with the same Apple ID via the iCloud screen of the Settings app. From one instance of the application add a new record to the database. When the notification alert appears on the other device or simulator (Figure 50-4), select it to launch the CloudKitDemo application which should, after a short delay, load and display the newly added record.
Repeat these steps both when the application on the second device is in the background and not currently running. In each case the behavior should be the same.