Search code examples
iosswiftcallkitcncontactviewcontroller

CNContactViewController handle CNContactPhoneNumbersKey along with CallKit


I have a VoIP application that use CallKit integration.

In my contacts screen I have a UITableView with all the device contacts and when the user press a contact I populate a CNContactViewController with:

extension ContactsViewController: UITableViewDelegate {
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        selectedContact = viewModel.contactAt(section: indexPath.section, index: indexPath.row)

        Logd(self.logTag, "\(#function) \(String(describing: selectedContact))")

        let contactViewController = CNContactViewController(for: selectedContact!)
        contactViewController.title = CNContactFormatter.string(from: selectedContact!,
                                                                style: CNContactFormatterStyle.fullName)
        contactViewController.contactStore = viewModel.contactStore
        contactViewController.allowsActions = false
        contactViewController.delegate = self
        navigationItem.titleView = nil

        navigationController?.pushViewController(contactViewController, animated: true)
        tableView.deselectRow(at: indexPath, animated: false)
    }
}

This populates the contacts details view without any problem.

I would like to catch the user action on the phone number pressing and perform the VoIP call so I use the following code:

extension ContactsViewController: CNContactViewControllerDelegate {
    func contactViewController(_ viewController: CNContactViewController,
                               shouldPerformDefaultActionFor property: CNContactProperty) -> Bool {
        if property.key == CNContactPhoneNumbersKey {
            let phoneNumberProperty: CNPhoneNumber = property.value as! CNPhoneNumber
            let phoneNumber = phoneNumberProperty.stringValue
            makeMyVoIPCall(number: phoneNumber!, video: false)
            //makeMyVoIPCall(number: "+1234567890", video: false)
            return false
        }
        if property.key == CNContactSocialProfilesKey {
            let profile: CNSocialProfile = property.value as! CNSocialProfile
            if profile.service == appServiceName {
                let phoneNumber = profile.username
                makeMyVoIPCall(number: phoneNumber!, video: false)
                return false
            }
        }

        Logd(self.logTag, "\(#function) nothing to handle for \(property)")
        return true
    }

    func contactViewController(_ viewController: CNContactViewController, didCompleteWith contact: CNContact?) {
        dismiss(animated: true, completion: nil)
    }
}

This has as a result when I press the phone item element to initiate 2 calls! One call is performed from my application (VoIP) and the other from the system (SIM/GSM).

What I tried:

  • Added the above code for handling the CNContactSocialProfilesKey and at this case the call is performed as expected only once via my application.
  • Changed the makeMyVoIPCall to a specific number instead of the pressed (see above commented line). Again I see 2 calls the system calls the clicked property and my application the "+1234567890".
  • I also verified that the return value should be false and not true when you handle the action.

What is required in order to tell the system that I am handling the action, and the SIM/GSM call should not performed?

I am testing on iOS 12.1.1 (16C50).


Solution

  • Wrapping the makeMyVoIPCall(number: phoneNumber!, video: false) in a main thread bundle solved the problem.

    I cannot really understand why iOS just ignore the false value otherwise.