0

The delegate for CNContactviewController is not called when properties get edited or selected.

When editing a new contact, the contactViewController(_ viewController: CNContactViewController, shouldPerformDefaultActionFor property: CNContactProperty) function is supposed to be called, but it's not.

How do you get notified when the user edits/selects a contact property?

Steps to reproduce:

  1. Copy the view controller below.
  2. Edit/select a contact property.

Expected behavior:

"yo" is printed every time you edit/select a property.

Actual behavior:

Nothing.

import Foundation
import Contacts
import ContactsUI


class ContactViewController: UIViewController, CNContactViewControllerDelegate {


    override func viewDidLoad() {
        super.viewDidLoad()

        createContact()
    }


    func createContact() {
        let contactController = CNContactViewController(forNewContact: nil)

        contactController.delegate = self
        contactController.allowsEditing = true
        contactController.allowsActions = true
        contactController.displayedPropertyKeys = [CNContactPostalAddressesKey, CNContactPhoneNumbersKey, CNContactGivenNameKey]

        contactController.view.layoutIfNeeded()

        present(contactController, animated:true)
    }


    // =============================================================================================================
    // MARK: CNContactViewControllerDelegate Functions
    // =============================================================================================================
    func contactViewController(_ viewController: CNContactViewController, didCompleteWith contact: CNContact?) {
        viewController.dismiss(animated: true, completion: nil)
        print("hi")
    }


    func contactViewController(_ viewController: CNContactViewController, shouldPerformDefaultActionFor property: CNContactProperty) -> Bool {
        print("yo")
        return true
    }


    // =============================================================================================================
    // MARK: UIViewController Functions
    // =============================================================================================================
    override var prefersStatusBarHidden: Bool {
        return true
    }


    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
}
Crashalot
  • 33,605
  • 61
  • 269
  • 439

1 Answers1

4

There are three initializers for making a CNContactViewController:

  • Existing contact: init(for:)
  • New contact: init(forNewContact:)
  • Unknown contact: init(forUnknownContact:)

The first and third forms call the delegate method contactViewController(_:shouldPerformDefaultActionFor:). The second form does not. That's the one you are using.

With the second flavor, the only event you get is contactViewController(_:didCompleteWith:), and at that point the new contact has already been saved into the database.

When editing a new contact, the contactViewController(_ viewController: CNContactViewController, shouldPerformDefaultActionFor property: CNContactProperty) function is supposed to be called

No, it isn't. That's just an idea you made up.

Expected behavior: "yo" is printed every time you edit/select a property.

Then stop expecting that.

How do you get notified when the user edits/selects a contact property?

You don't.

When you use a framework like Cocoa, you don't get to make up any expectations you like. Your expectations need to be based on what the framework actually does. You might wish that CNContactViewController and its delegate messages worked as you describe, and that might make a very good enhancement request to Apple. But it is not how it works in fact, so expecting it to do so won't do you any good.

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • OK thanks. The documentation `contactViewController(_:shouldPerformDefaultActionFor:)` is extremely sparse. What does this function do then? Is there no way to know if a user changed a contact property? Thanks again! – Crashalot Dec 04 '18 at 01:41
  • Well, the whole question doesn't make much sense to me. With `init(forNewContact:)` you are asking the user to create _a new contact_. There is no point knowing when the user is half-done with that task. Either the user has created a new contact or not, and you'll find out all about it when it's over. What would be the use of knowing what the user is doing in the middle? Still, as I say, if you have a good use case, submit it to Apple. More disturbing to me is that by the time you get any event, the contact has already been saved into the database and you can't prevent that. – matt Dec 04 '18 at 02:04
  • Thanks for the response. The goal is to reuse the contact form since it has localization logic for addresses and phone numbers, then store the contact information elsewhere. This isn't for storing contacts in the user's address book, but the app's. – Crashalot Dec 04 '18 at 02:06
  • Yes, that's the problem I just mentioned: you are not given any interface for doing that. The best you can do is delete the contact from the database right after the user stores it (I've done it that way). – matt Dec 04 '18 at 02:09
  • OK thanks! By chance, do you know if it's possible to only show a subset of fields (e.g., address, phone) during editing? – Crashalot Dec 04 '18 at 02:10
  • Only if you use the CNContactPickerViewController, I believe. – matt Dec 04 '18 at 02:20