How to pull a contacts mailing address using the mailing address label?

I have a situation where I need to allow the user to select a contacts mailing address if the contact has multiple addresses. If there are multiple addresses I present the user with a tableView with the names of the addresses. When the user taps one of the address names I want to pull the address data and populate a field. What I need help with is getting a contacts mailing address using the mailing address label?


The function buildContactsAddress_Array below builds an array containing the address label(name) and the address ID. The array is used to populate a tableView where the user can select the address by its name. I have included pretty much all of the related code to try and make things as clear as I can. Thanks in advance.


This is the part I want to change or replace so it uses the addressID. Right now it just uses the first/home address.


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


struct contactAddresses
    {
        var theLabel: String
        var theID: String
    }
   
    private var addressesArray = [contactAddresses]()
    private var addressID: String = ""
    private var theContactID: String = ""



This function pulls the contacts info using the contacts ID.


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 firstPostalAddress = (theName.postalAddresses.first),
                let labelValuePair = firstPostalAddress.value(forKey: "labelValuePair") as? NSObject,
                let finalPostalAddress = labelValuePair.value(forKey: "value") as? CNPostalAddress
            {
                mailAddress = CNPostalAddressFormatter.string(from: finalPostalAddress, style: .mailingAddress)
            }
        }



This function puts the contacts addresses into an array. The array is then used to populate a tableView.


func buildContactsAddress_Array(contactID: String)
    {
        let store = CNContactStore()
        var theName = CNContact()
      
        let theKeys = [CNContactPostalAddressesKey] as [CNKeyDescriptor]
      
        do {
            theName = try store.unifiedContact(withIdentifier: contactID, keysToFetch: theKeys)
  
            let postalAddress = theName.postalAddresses
            postalAddress.forEach { (mailAddress) in
              
                // Strip forst 4 and last 4 from _$!!$_
                let aaa = mailAddress.label
                let bbb = aaa!.dropLast(4)
                let ccc = bbb.dropFirst(4)
              
                addressesArray.append(contactAddresses(theLabel: String(ccc), theID: mailAddress.identifier))
            }
          
            addressesArray.sort { $0.theLabel < $1.theLabel }
          
        } catch {
            print("Fetching contact addresses failed: \(error)")
        }
    }



This is the tableView extension. When a cell is tapped, addressID is populated with the ID of the appropriate mailing address.


extension QuotePreview_VC: UITableViewDelegate, UITableViewDataSource
    {
        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
        {
            return addressesArray.count
        }
       
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
        {
            let theCell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
           
            theCell.textLabel?.text = addressesArray[indexPath.row].theLabel
           
            return theCell
        }
       
        func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
        {
            addressID = addressesArray[indexPath.row].theID
            populateThePrintFld()
            closeThePicker()
        }
    }
Answered by OOPer in 414624022

In my opinion, you should better declare `addressID` as Optional. As Swift has many functionality for using Optionals and it is easier to check if Optional has a non-nil vallue than checking if String has a non-empty value.

    private var addressID: String? = nil

Including this change, you can write something like this:

        if let addressID = addressID {
            if let finalPostalAddress = theName.postalAddresses.first(where: {labelValuePair in labelValuePair.identifier == addressID})?.value {
                mailAddress = CNPostalAddressFormatter.string(from: finalPostalAddress, style: .mailingAddress)
            }
        }

Please remember, you have no need to use `value(forKey:)` if you use the right properties.

Accepted Answer

In my opinion, you should better declare `addressID` as Optional. As Swift has many functionality for using Optionals and it is easier to check if Optional has a non-nil vallue than checking if String has a non-empty value.

    private var addressID: String? = nil

Including this change, you can write something like this:

        if let addressID = addressID {
            if let finalPostalAddress = theName.postalAddresses.first(where: {labelValuePair in labelValuePair.identifier == addressID})?.value {
                mailAddress = CNPostalAddressFormatter.string(from: finalPostalAddress, style: .mailingAddress)
            }
        }

Please remember, you have no need to use `value(forKey:)` if you use the right properties.

Thank you very much! I've been struggling with this for over a week.
After I posted this question I was thinking whether I should use the address ID or the address label to perform this operation. My thought is that if the user gets a new phone and loads the app then the address IDs would be different and that might cause a problem. However, the address label would be the same. Am I over thinking this?

Everything may change while user edits contacts.


But, as described in the doc of `identifier`,

The identifier can be persisted between the app launches.


So, identifier may be a good key to identify some LabeledValue.

If you can keep both addressID and label, the label can be a second-chance key, when no address of the same identifier cannot be found.

How to pull a contacts mailing address using the mailing address label?
 
 
Q