13

I want to add one custom hidden field in iOS mobile contacts from my application.

Is it possible to add the new hidden field with value in iOS mobile contacts? Will CNMutableContact allow me to add the new custom property?

bjtitus
  • 4,231
  • 1
  • 27
  • 35
Manikandan
  • 131
  • 4

1 Answers1

4

The short answer sadly is no, thats not possible.

The long answer is: CNMutableContact is subclassing CNContact which comes with the following public interface.

open class CNContact : NSObject, NSCopying, NSMutableCopying, NSSecureCoding {
    open var identifier: String { get }
    open var contactType: CNContactType { get }
    open var namePrefix: String { get }
    open var givenName: String { get }
    open var middleName: String { get }
    open var familyName: String { get }
    open var previousFamilyName: String { get }
    open var nameSuffix: String { get }
    open var nickname: String { get }
    open var organizationName: String { get }
    open var departmentName: String { get }
    open var jobTitle: String { get }
    open var phoneticGivenName: String { get }
    open var phoneticMiddleName: String { get }
    open var phoneticFamilyName: String { get }
    open var phoneticOrganizationName: String { get }
    open var note: String { get }
    open var imageData: Data? { get }
    open var thumbnailImageData: Data? { get }
    open var imageDataAvailable: Bool { get }
    open var phoneNumbers: [CNLabeledValue<CNPhoneNumber>] { get }
    open var emailAddresses: [CNLabeledValue<NSString>] { get }
    open var postalAddresses: [CNLabeledValue<CNPostalAddress>] { get }
    open var urlAddresses: [CNLabeledValue<NSString>] { get }
    open var contactRelations: [CNLabeledValue<CNContactRelation>] { get }
    open var socialProfiles: [CNLabeledValue<CNSocialProfile>] { get }
    open var instantMessageAddresses: [CNLabeledValue<CNInstantMessageAddress>] { get }
    open var birthday: DateComponents? { get }
    open var nonGregorianBirthday: DateComponents? { get }
    open var dates: [CNLabeledValue<NSDateComponents>] { get }
    /* [...] functions */
}

The only necessary difference between the two types (which makes it mutable) is, that apart from the identifier property the CNMutableContact's properties are not specified as get-only's. When having a closer look, you can now see, that there is no possibility for custom properties on Contact objects. Subclassing the CNMutableContact like I did in the following example will result in nilError and the CNContactStore not storing our custom contact.

func saveCustomContact() {
    let contactStore = CNContactStore()
    let contact = MyContact()

    contact.givenName = "John"
    contact.familyName = "Doe"
    contact.test = "Hello World"

    do {
        let saveRequest = CNSaveRequest()
        saveRequest.add(contact, toContainerWithIdentifier: nil)
        try contactStore.execute(saveRequest)
    } catch {
        print(error)
    }
}

func retrieveCustomContact() {
    DispatchQueue.global().async {
        let keysToFetch = [CNContactFormatter.descriptorForRequiredKeys(for: .fullName),CNContactPhoneNumbersKey] as [Any]
        let fetchRequest = CNContactFetchRequest( keysToFetch: keysToFetch as! [CNKeyDescriptor])
        CNContact.localizedString(forKey: CNLabelPhoneNumberiPhone)

        fetchRequest.mutableObjects = false
        fetchRequest.unifyResults = true
        fetchRequest.sortOrder = .userDefault

        do {
            try CNContactStore().enumerateContacts(with: fetchRequest) { (contact, stop) -> Void in
                guard let contact = contact as? MyContact else { print("damn - it's not working!"); return }
                print(contact.test)
            }
        } catch {
            print(error)
        }
    }
}

open class MyContact: CNMutableContact {
    open var test: String?
}

This brings me to the conclusion, that apple doesn't want us to store custom fields in the default contact's book, which is easily understandable from a sync (serialisation/deserialisation) perspective.

cr0ss
  • 533
  • 4
  • 18
  • Thanks @cr0ss I like how you shared the public interface here. That helps. Re-purposing a field sounds like a workaround, but yet still visible to the end user. Bummer. :) Much appreciated, regardless! – finneycanhelp Dec 25 '17 at 22:00
  • Thanks for the answer @cr0ss – Manikandan Apr 03 '19 at 10:13
  • I'm trying to work within Apple guidelines here. There doesn't seem to be any alternative method at present, but a risk to using an existing field is that the user may decide to repurpose that field for something else in Contacts.app, and tread on your field data. – ICL1901 Mar 29 '21 at 19:30