CNContactBirthdayKey seems to be ignored in ContactsUI ViewControllers

When I try to filter the Contacts properties with displayedPropertyKeys, there is something wrong with the key CNContactBirthdayKey. Even though my contact has a birthday date set, I cant see it in any ContactsUI's ViewController. In the other hand, the properties related to the phone number, or email address seem to work well. I tested it on IOS 12.1 .

Can you help me ?


Here is the code in Swift 4 :


import UIKit
import ContactsUI

class MyViewController: UIViewController, CNContactPickerDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let contacts = fetchContactsWithBirthday()
        
        let vc = CNContactPickerViewController()
        
        vc.delegate = self
        vc.displayedPropertyKeys = [CNContactNonGregorianBirthdayKey, CNContactBirthdayKey, CNContactDatesKey]
        vc.predicateForEnablingContact = NSPredicate(format: "birthday.@count > 0")
        
        self.present(vc, animated: true, completion: nil)
        
        
        let vc2 = CNContactViewController(for: contacts.first!)
        
        vc2.displayedPropertyKeys = [CNContactNonGregorianBirthdayKey, CNContactBirthdayKey, CNContactDatesKey]
        
        self.navigationController?.pushViewController(vc2, animated: true)
    }

    func fetchContactsWithBirthday() -> [CNContact] {
        
        var contacts: [CNContact] = []
        
        let contactStore = CNContactStore()
        let keys = CNContactViewController.descriptorForRequiredKeys()
        let fetchReq = CNContactFetchRequest.init(keysToFetch: [CNContactGivenNameKey,CNContactFamilyNameKey, CNContactBirthdayKey, CNContactNicknameKey, CNContactImageDataAvailableKey, CNContactImageDataKey, CNContactThumbnailImageDataKey] as [CNKeyDescriptor])
        fetchReq.keysToFetch.append(keys)
        do {
            try contactStore.enumerateContacts(with: fetchReq) {
                (contact, end) in
                
                if contact.birthday != nil {
                    contacts.append(contact)
                }
            }
        }
        catch {
            print(error)
        }
        
        return contacts
    }
}

AppDelegate :


    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
       
        window = UIWindow(frame: UIScreen.main.bounds)
        window?.makeKeyAndVisible()
       
        let vc = MyViewController()
       
        window?.rootViewController = UINavigationController(rootViewController: vc)
       
        return true
    }

Replies

Try adding an if with isKeyAvailable, logging the result so you can follow along via the console.

Where is fetchContactsWithBirthday defined ?


Is it a func of yours ?

Yes, look at the post : defined below viewDidLoad()


🙂

So much for me.


Effectively, I tested with a lot of ContactKeys on simulator, no birtday date shows


CNContactNonGregorianBirthdayKey, CNContactBirthdayKey, CNContactDatesKey, CNContactPhoneNumbersKey, CNContactEmailAddressesKey, CNContactDatesKey, CNContactPostalAddressesKey


But the predicate does effectively filter contacts which means birthday is effectively read.


I tested with XCode 9.4 and IOS 10.1 deployment target. Same result.

But contact.birthday shows !


                if contact.birthday != nil {
                    contacts.append(contact)
                    print(contact.familyName, contact.birthday!)
                }


yields


Bell calendar: gregorian (fixed) year: 1978 month: 1 day: 20 isLeapMonth: false

Appleseed calendar: gregorian (fixed) year: 1980 month: 6 day: 22 isLeapMonth: false

Haro calendar: gregorian (fixed) year: 1985 month: 8 day: 29 isLeapMonth: false

Taylor calendar: gregorian (fixed) year: 1998 month: 6 day: 15 isLeapMonth: false


In fact, I get Anniversary (not birthday) displayed for Haro, because I used the key CNContactDatesKey


Printing the contact

print(contact)


shows all fieds, but not birthday

<CNContact: 0x7fe581616330: identifier=410FE041-5C4E-48DA-B4DE-04C15EA3DBAC, givenName=John, familyName=Appleseed, organizationName=(null), phoneNumbers=(

"<CNLabeledValue: 0x600003931600: identifier=E297F1F7-CAFC-4A9D-ABF8-F79DB4496C87, label=_$!<Mobile>!$_, value=<CNPhoneNumber: 0x600002c03380: stringValue=888-555-5512, initialCountryCode=(null)>>",

"<CNLabeledValue: 0x600003931680: identifier=5E423897-5B64-4129-AF55-10B1B3153697, label=_$!<Home>!$_, value=<CNPhoneNumber: 0x600002c03400: stringValue=888-555-1212, initialCountryCode=(null)>>"

), emailAddresses=(

"<CNLabeledValue: 0x600003931700: identifier=172726CF-4C0A-44C3-B9D8-0C86F7E654AD, label=_$!<Work>!$_, value=John-Appleseed@mac.com>"

), postalAddresses=(

"<CNLabeledValue: 0x600003931780: identifier=2EAD19A1-01AC-4A46-A27E-A9831C9B3245, label=_$!<Work>!$_, value=<CNPostalAddress: 0x600000f79fe0: street=3494 Kuhl Avenue, subLocality=, city=Atlanta, subAdministrativeArea=, state=GA, postalCode=30303, country=USA, countryCode=us>>",

"<CNLabeledValue: 0x600003931800: identifier=4F2BAAD4-98DE-459B-99CE-A2BF15408396, label=_$!<Home>!$_, value=<CNPostalAddress: 0x600000f7a030: street=1234 Laurel Street, subLocality=, city=Atlanta, subAdministrativeArea=, state=GA, postalCode=30303, country=USA, countryCode=us>>"

)>


When I test keyAvailable (in fetch function)

        do {
            try contactStore.enumerateContacts(with: fetchReq) {
                (contact, end) in
                print("   --> ", contact.familyName, "CNContactBirthdayKey", contact.isKeyAvailable(CNContactBirthdayKey), "CNContactFamilyNameKey", contact.isKeyAvailable(CNContactFamilyNameKey) )

                if contact.birthday != nil {
                    contacts.append(contact)
                    print("With birthday", contact.familyName, contact.birthday!, contact.dates)
                }
            }
        }
        catch {
            print(error)
        }

I get true for all


--> Bell CNContactBirthdayKey true CNContactFamilyNameKey true

With birthday Bell calendar: gregorian (fixed) year: 1978 month: 1 day: 20 isLeapMonth: false []

--> Higgins CNContactBirthdayKey true CNContactFamilyNameKey true

--> Appleseed CNContactBirthdayKey true CNContactFamilyNameKey true

With birthday Appleseed calendar: gregorian (fixed) year: 1980 month: 6 day: 22 isLeapMonth: false []

--> Haro CNContactBirthdayKey true CNContactFamilyNameKey true

With birthday Haro calendar: gregorian (fixed) year: 1985 month: 8 day: 29 isLeapMonth: false [<CNLabeledValue: 0x60000186c980: identifier=59B1793A-5E6D-441C-A91F-7DE768AAC7A0, label=_$!<Anniversary>!$_, value=<NSDateComponents: 0x600003aecc60>

Calendar: <_NSCopyOnWriteCalendarWrapper: 0x600000db99a0>

Calendar Year: 2002

Month: 2

Leap month: no

Day: 15>]

--> Zakroff CNContactBirthdayKey true CNContactFamilyNameKey true

--> Taylor CNContactBirthdayKey true CNContactFamilyNameKey true

With birthday Taylor calendar: gregorian (fixed) year: 1998 month: 6 day: 15 isLeapMonth: false []


Starts to smell like a bug or at least poorly documented feature.


Same situation noted 18 months ago

https://stackoverflow.com/questions/44744552/unable-to-retrieve-date-of-birth-of-a-contact-in-ios-simulator

Did you file a bug report ? Probably will be marked as Duplicate, or you will get a solution to this issue.

Yes I did.


But I am not sure about the conclusion of your tests.

I didn't have any issue with the fetch method in IOS 12. The birthday key is fetched very well.

The problem is located in the ContactsUI's view controllers.


So, to me, there is no link to the stackoverflow post you mentioned.

I've seen that birthday is fetched in contact, as birthday is correctly displayed in print

gregorian (fixed) year: 1978 month: 1 day: 20 isLeapMonth: false


So, you may be right, that may be a viewController issue.

>The problem is located in the ContactsUI's view controllers.


Might want to burn a support ticket w/DTS via Account / Code-Level Support, supplying a project that demos the issue to see if they have any opinion.

OK, so I think I've had the same issue for a while now, but now I found a solution to it (or at least my problem).

This was my existing code:
Code Block swift
let contact = CNMutableContact()
contact.birthday = Calendar.current.dateComponents([.day, .month, .year, .hour, .minute, .second], from: person.dateOfBirth)

The contact object got a birthday, and I could print it. When I used the contact in a CNContactViewController, the birthday field was showing, but without a date. If I tried to check if the date was valid I got false: contact.birthday?.isValidDate

After I changed the code to this it now works fine:
Code Block swift
contact.birthday = Calendar.current.dateComponents([.day, .month, .year, .hour, .minute, .second], from: person.dateOfBirth)
    contact.birthday?.calendar = Calendar.current

The birthday field in the contact view contains the date, and contact.birthday?.isValidDate returns true.