Search code examples
iosswift5xcode11.3

Fetching and formatting contact name and address swift 5


I'm pulling the contacts name, mailing address and organization from the contacts list and I have some questions. I'm getting the contacts identifier and storing it to the SQLite DB. I then pull the ID from the DB and pass it to the function. ie. contactID This works fine. However, I'm having 1 problem and I have a question.

Problem: Everything works fine unless the contacts address is empty. In this case the "if let" statements is throwing this error. I thought the "if let" was supposed to prevent this. Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArray0 objectAtIndex:]: index 0 beyond bounds for empty NSArray'

What can I do to fix this, or have I coded myself into a corner?

I also have a of question. How do I check to see if there are multiple mailingAddresses for the contact? If there are multiple mailingAddresses then my plan is to present an alert to allow the user to pick the one to use. Presenting the alert isn't a problem, I just need help with checking for and getting the addresses.

func getContactFromID_Ouote(contactID: String)
    {
        let store = CNContactStore()
        var theName = CNContact()

        let theKeys = [CNContactNamePrefixKey,
                       CNContactGivenNameKey,
                       CNContactFamilyNameKey,
                       CNContactOrganizationNameKey,
                       CNContactPostalAddressesKey,
                       CNContactFormatter.descriptorForRequiredKeys(for: .fullName)] as! [CNKeyDescriptor]

        do {
            theName = try store.unifiedContact(withIdentifier: contactID, keysToFetch: theKeys)

            contactName = CNContactFormatter.string(from: theName, style: .fullName)!

            contactPrefix = theName.namePrefix
            contactFirst = theName.givenName
            contactLast = theName.familyName
            companyName = theName.organizationName == "" ? "" : theName.organizationName

        } catch {
            print("Fetching contact data failed: \(error)")
        }

        if let addressString = (((theName.postalAddresses[0] as AnyObject).value(forKey: "labelValuePair") as AnyObject).value(forKey: "value")) as? CNPostalAddress
        {
            mailAddress = CNPostalAddressFormatter.string(from: addressString, style: .mailingAddress)
        }
}

Solution

  • First of all, the if let would check whether the addressString is nil or not. And not the parts of this expression.
    (((theName.postalAddresses[0] as AnyObject).value(forKey: "labelValuePair") as AnyObject).value(forKey: "value")) as? CNPostalAddress

    The above expression is executed step by step, like first (theName.postalAddresses[0] as AnyObject) would be executed,
    then value(forKey: "labelValuePair") as AnyObject),
    then .value(forKey: "value")) as? CNPostalAddress.
    And if any of these expression is nil, it would crash, but only if the last expression i.e. value(forKey: "value")) as? CNPostalAddress is nil, it wont crash and work as per your expectation.

    Second thing is theName.postalAddresses[0] uses subscript, to fetch the object at index 0, and this is always forced-unwrapped. Hence it doesn't check for nil and crashes.
    What you must do is something like this.

    if let firstPostalAddress = (theName.postalAddresses.first as? AnyObject),
                    let labelValuePair = firstPostalAddress.value(forKey: "labelValuePair") as? AnyObject,
                let finalPostalAddress = labelValuePair.value(forKey: "value") as? CNPostalAddress
                {
                    mailAddress = CNPostalAddressFormatter.string(from: finalPostalAddress, style: .mailingAddress)
    
    }
    

    So what you are doing is checking for each object whether it is nil or not. Hope this helps.