CloudKit Discoverability

Discoverability is a CloudKit feature that lets you:

  1. Fetch the user’s First Name and Last Name from their iCloud account.
  2. Fetch the user’s friends First Name and Last name from the user’s address book.
caution Code presented on this page assume you are using Xcode 6.4 and Swift version 1.2. So if you are using a newer version of Swift, it may produce errors. Fix them by using Xcode’s Fix-it tool. I assume you are a registered member of Apple’s iOS Developer Program, and you have a real iPhone/iPad device to test the CloudKit code presented here.

Now, Figure 1 and 2 shows outputs you will see on the simulator’s screen, once we’ve implemented above tasks in the MainViewController.swift class file.

Figure 1
Figure 1

Figure 2
Figure 2

With that said, click the MainViewControllers.swift file to load it in the standard code editor. Next, enter these statements above the viewDidLoad() function:

let container = CKContainer.defaultContainer()
var friendsList = [String]()
var userName = String()
let emojiBullet = "  ➢ "

Next, enter this statement in the viewDidAppear() function:

textView.text = "Hello user!"

Next, enter this code in the executeCodeButton() function:

@IBAction func executeCodeButtonClicked() {
        textView.text = "Hello user!"
        self.container.statusForApplicationPermission(.PermissionUserDiscoverability, completionHandler: {
                (status, error) in
                if error != nil {
                    NSOperationQueue.mainQueue().addOperationWithBlock {
                        self.textView.text = "Cloud Error\n\(error.localizedDescription)"
                    }
                } else {
                    if status == .InitialState {
                        self.requestPermissionToBeDiscovered()
                    } else if status == .Granted {
                        self.fetchUserName({ (appUserName) -> Void in
                            self.userName = appUserName
                            self.fetchAndDisplayUserContacts()
                        })
                    }
                }
        })
    }

Code you entered in the executeCodeButton() function perform these tasks:

  1. Find out whether the user already granted the app permission to access her personal information. This is done by calling the statusForApplicationPermission() function on the container. The function returns either an error or an applicationPermissionStatus (status for short).
  2. If the status contain the value InitialState, the app ask the user for permission to access her personal information. In other words, the app request permission to be discovered. The user will see the dialog view shown in Figure 1 above, on her screen.
  3. The app fetch the user’s personal information and her friends’ personal information, and display them in the textView-see Figure 2 above; only if the user granted the app permission to access her personal information. She does this by clicking the OK button on the dialog view.

Now, here’s the code to implement the user defined functions called in the executeCodeButton() function.

func requestPermissionToBeDiscovered() {
        self.container.requestApplicationPermission(.PermissionUserDiscoverability, completionHandler: {(status, error) in
            if error != nil {
                // Update the textView on the main thread
                NSOperationQueue.mainQueue().addOperationWithBlock {
                    self.textView.text = "Cloud Error\n\(error.localizedDescription)"
                }
            } else if status == .Granted {
                // The user granted the app permission to be discovered
                self.fetchUserName({ (appUserName) -> Void in
                    self.userName = appUserName
                })
            }
        })
    }

    func fetchUserName(completionHandler: (appUserName: String) -> Void) {
        // Get the app user's recordID
        self.container.fetchUserRecordIDWithCompletionHandler {
            (recordId, error) in
            if error != nil {
                // Update the textView on the main thread
                NSOperationQueue.mainQueue().addOperationWithBlock {
                    self.textView.text = "Cloud Error\n\(error.localizedDescription)"
                }
            }

            // Use the recordID to fetch the user information from the CloudKit container
            self.container.discoverUserInfoWithUserRecordID(recordId) { (userRecord, fetchError) in
                if fetchError != nil {
                    // Update the textView on the main thread
                    NSOperationQueue.mainQueue().addOperationWithBlock {
                        self.textView.text = "Cloud Error\n\(error.localizedDescription)"
                    }
                } else {
                    // Put the user's full name in a string variable
                    let fullName = userRecord.firstName + " " + userRecord.lastName

                    // Return the user's fullName to the caller of this function
                    completionHandler(appUserName: fullName)
                }
            }
        }
    }

    func fetchAndDisplayUserContacts() {
        self.container.discoverAllContactUserInfosWithCompletionHandler( {userContacts, error in
            if error != nil {
                // Update the textView on the main thread
                NSOperationQueue.mainQueue().addOperationWithBlock {
                    self.textView.text = "Cloud Error\n\(error.localizedDescription)"
                }
            } else {
                var friendName = String()
                for contact in userContacts as! [CKDiscoveredUserInfo] {
                    // Add the user's friend name in the friendsList array
                    self.friendsList.append(self.emojiBullet+contact.firstName +  " " + contact.lastName)
                }

                // Convert elements in the friendsList array to a string
                let stringFriendsList = "\n".join(self.friendsList)

                // Put the textView value in a variable
                let totalContacts = "\n\(userContacts.count) contacts came back\n."

                // On the main thread, display the user's friends names in the textView
                NSOperationQueue.mainQueue().addOperationWithBlock {
                    self.textView.text = "Hey \(self.userName)!\n\nHere is a list of your friends who are using this app and agreed to be discovered:\n\n\(stringFriendsList)"
                }
            }
        })
    }

When you finish implementing above functions in the mainViewController.swift file, run the app in the iPhone 6 Simulator. You will see the message, “Hello user!” in the text view. Click the Execute Code button and you will see the dialogue shown in Figure 1, on the simulator’s screen. Click the OK button and you will see this output in the textView.

cloudkit-figure1-8c
Figure 3

As you can see no names appear in the textView. That’s because the user’s friends did not install the app on their devices and agreed to be discovered. So add some fake friends names in the friendsList array.

override func viewDidLoad() {
  ...
  friendsList.append("\(emojiBullet)Mary Edwards")
  friendsList.append("\(emojiBullet)Oleg Zakala")
  friendsList.append("\(emojiBullet)Amand Marshall")
  friendsList.append("\(emojiBullet)Jason Alexander")
}

Now, run the app in the simulator and click the Execute Code button. This time, you will see output shown in Figure 2, on the simulator’s screen.