Tuesday, May 16, 2017

iOS 10 CallKit Directory Extension - blocking and identifying callers (Updated)

This an update to an earlier blog post that I wrote when this API was still in beta. http://iphoneramble.blogspot.com/2016/07/ios-10-callkit-directory-extension.html This post provides more up to date information and some example code.


New in iOS 10, Apple introduced CallKit. One of the things it does is provide an extension interface that allows your app to provide a block list and a caller directory.

Apple just briefly mentioned this feature in the "Whats new in Cocoa Touch" session 205 at WWDC 2016. All the information they provide is on slides 270 to 273.

https://developer.apple.com/videos/play/wwdc2016/205/
http://asciiwwdc.com/2016/sessions/205

Here is what Apple's api looks like in iOS 10:

public class CXCallDirectoryExtensionContext : NSExtensionContext {
    public func addBlockingEntry(withNextSequentialPhoneNumber phoneNumber: CXCallDirectoryPhoneNumber)
    public func addIdentificationEntry(withNextSequentialPhoneNumber phoneNumber: CXCallDirectoryPhoneNumber, label: String)
}

Here is Apple's documentation: https://developer.apple.com/reference/callkit/cxcalldirectoryprovider

If you compare the headers with the documentation, you will notice that Apple's documentation is not quite up to date. The phone number that is passed in as an Integer and not a String. This was changed between the beta releases of iOS 10 and iOS 10.2.

Unfortunately Apple doesn't provide any sample code for this API. When I did a Google search, I found a bunch of confused people. And then this Chinese guy named Collin. Google Translate to the rescue!
http://colin1994.github.io/2016/06/17/Call-Directory-Extension-Study/
https://translate.google.com/translate?hl=en&sl=zh-CN&u=http://colin1994.github.io/2016/06/17/Call-Directory-Extension-Study/&prev=search

Collin does a pretty good job of walking you through creating a sample project. I'm going to reproduce some of it here (in English) and add a few things I learned. His focus was a blocking app. I want to write an app that can provide a caller directory.

How to create your own directory

1. Create a project. Xcode -> File -> New -> Project. Pick any type. It doesn't really matter.

2. Add an extension target. Xcode -> File -> New -> Target. Pick Application Extension on the left column and then Call Directory Extension.

One thing to note here. This extension and the app must be 64-bit only. That means this will only run on an iPhone 5s or later. (No iPhone 5 or iPhone 5c)

3. Open up the extension that will be CallDirectoryHandler.swift by default. Edit addIdentificationPhoneNumbers to insert the directory information that you want.

        let phoneNumbers: [CXCallDirectoryPhoneNumber] = [ 1866_111_1111, 1_877_555_5555, 1888_555_5555 ]
        let labels = [ "Bill Ding", "Ann Teak", "Barry Cade" ]

        for (phoneNumber, label) in zip(phoneNumbers, labels) {
            context.addIdentificationEntry(withNextSequentialPhoneNumber: phoneNumber, label: label)
        }

4. Run the app on your iPhone

5. Switch over to the iOS Settings App and go to Phone -> Call Blocking & Identification and give permission to use your app.

6. Make your test phone call


In my experimenting, I learned a few key things.

a) You need to add code to force the app to reload the extension in order to load directory data. This can be done by switching the permissions for the app off and back on. Or you can do it by calling reloadExtension. (see the sample code on Github for a good example of how to do this)
b) You must provide a full country code and area code with the phone number. For example, 1_877_111_1111 will work. 877_111_1111 and 111_1111 will not work.
c) You do NOT need to have an entry in the call blocking list to get the call directory bits to work.
d) Of course, if you already have a contact in Apple's Contacts app with the incoming phone number, that gets priority.
e) If Apple has found the incoming phone number somewhere already and wants to give you a "maybe" suggestion, that gets priority over your app. What that meant for me is that deleting my home phone number from all my contacts was not good enough for testing. I had to get more creative with coming up with a test phone number.
f) The label text that you see will have the following format:
  APP NAME Caller ID: John Doe
You can change the app name part of the label by changing app's Bundle Name.

Reference

Our friends at raywenderlich.com wrote up a nice post that covers CallKit in general. It includes a section that also covers working with the call directory/blocking api.
https://www.raywenderlich.com/150015/callkit-tutorial-ios

Sample Code

Sample code helps a lot. I put together a sample project that walks through detecting whether or not the extension is enabled, and loading some phone numbers from the extension. Take a look at https://github.com/dvshelley/CallDirectory for some working code.