Search code examples
swiftcontacts

swift update contact fails


In my app, I collect data from clients like names, addresses, phone numbers etc. I'd like to offer a button for saving and updating these data to apple's contacts app.

Therefor I wrote this class:

class ContactsClass {
    func addToContacts(
        lastName: String,
        firstName: String,
        mail: String = "",
        phone1: String = "",
        street: String,
        city: String,
        postCode: String
    ) {
        // Create a mutable object to add to the contact
        let contact = CNMutableContact()
            
        contact.givenName = firstName
        contact.familyName = lastName
        
        if mail != "" {
            let homeEmail = CNLabeledValue(label: CNLabelHome, value: mail as NSString)
            contact.emailAddresses = [homeEmail]
        }
        
        contact.phoneNumbers = []
        if phone1 != "" {
            contact.phoneNumbers.append(CNLabeledValue(label: CNLabelPhoneNumberMain, value: CNPhoneNumber(stringValue: phone1)))
        }

        let homeAddress = CNMutablePostalAddress()
        homeAddress.street = street
        homeAddress.city = city
        homeAddress.postalCode = postCode
        contact.postalAddresses = [CNLabeledValue(label: CNLabelHome, value: homeAddress)]

        // Save the newly created contact
        let store = CNContactStore()
        let saveRequest = CNSaveRequest()
        saveRequest.add(contact, toContainerWithIdentifier: gruppe)
        do {
            try store.execute(saveRequest)
        } catch {
            print("Saving contact failed, error: \(error)")
            // Handle the error
        }
    }
    
    
    func checkForExistingContact(familyName: String, givenName: String) -> CNContact? {
        let store = CNContactStore()
        do {
            let predicate = CNContact.predicateForContacts(matchingName: familyName)
            let keysToFetch = [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactEmailAddressesKey] as [CNKeyDescriptor]
            let contacts = try store.unifiedContacts(matching: predicate, keysToFetch: keysToFetch)
            for contact in contacts {
                if contact.givenName == givenName {
                    return contact
                }
            }
            return nil
        } catch {
            return nil
        }
    }
    
    
    func updateContact(
        contact: CNContact,
        lastName: String,
        firstName: String,
        mail: String = "",
        phone1: String = "",
        street: String,
        city: String,
        postCode: String
    ) {
            let mutableContact = contact.mutableCopy() as! CNMutableContact
            
            mutableContact.givenName = firstName
            mutableContact.familyName = lastName
            
            if mail != "" {
                let homeEmail = CNLabeledValue(label: CNLabelHome, value: mail as NSString)
                mutableContact.emailAddresses = [homeEmail]
            }
            
            mutableContact.phoneNumbers = []
            if phone1 != "" {
                mutableContact.phoneNumbers.append(CNLabeledValue(label: CNLabelPhoneNumberMain, value: CNPhoneNumber(stringValue: phone1)))
            }
            
            let homeAddress = CNMutablePostalAddress()
            homeAddress.street = street
            homeAddress.city = city
            homeAddress.postalCode = postCode
            mutableContact.postalAddresses = [CNLabeledValue(label: CNLabelHome, value: homeAddress)]
            
            
            // Save the updated created contact
            let store = CNContactStore()
            let saveRequest = CNSaveRequest()
            saveRequest.update(mutableContact)
            do {
                try store.execute(saveRequest)
            } catch {
                print("Saving contact failed, error: \(error)")
                // Handle the error.
            }
    }
}

My button calls this func

    private func addToContacts() {
        let con = ContactsClass()
        
        if let contact = con.checkForExistingContact(familyName: klient.nachname!, givenName: klient.vorname!) {
            con.updateContact(
                contact: contact,
                lastName: klient.nachname!,
                firstName: klient.vorname!,
                mail: klient.email ?? "",
                phone1: klient.telefon1 ?? "",
                street: selectedAddress?.strasse ?? "",
                city: selectedAddress?.ort ?? "",
                postCode: selectedAddress?.plz ?? ""
            )
        } else {
            con.addToContacts(
                lastName: klient.nachname!,
                firstName: klient.vorname!,
                mail: klient.email ?? "",
                phone1: klient.telefon1 ?? "",
                street: selectedAddress?.strasse ?? "",
                city: selectedAddress?.ort ?? "",
                postCode: selectedAddress?.plz ?? ""
            )
        }
    }

My problem:

  • Adding the contact works fine. No problems.
  • Updating this contact doesn't. Error in log:
2023-06-21 21:28:25.166437+0200 justCare 2[64058:433979] [core] Attempted to register account monitor for types client is not authorized to access: {(
    "com.apple.account.CardDAV",
    "com.apple.account.Exchange",
    "com.apple.account.LDAP"
)}
2023-06-21 21:28:25.166546+0200 justCare 2[64058:433979] [accounts] CNAccountCollectionUpdateWatcher 0x6000020a04c0: Store registration failed: Error Domain=com.apple.accounts Code=7 "(null)"
2023-06-21 21:28:25.166605+0200 justCare 2[64058:433979] [accounts] CNAccountCollectionUpdateWatcher 0x6000020a04c0: Update event received, but store registration failed. This event will be handled, but the behavior is undefined.
2023-06-21 21:28:25.340559+0200 justCare 2[64058:433979] [api] Attempt to read notes by an unentitled app
2023-06-21 21:28:25.343112+0200 justCare 2[64058:433979] [General] A property was not requested when contact was fetched.
2023-06-21 21:28:25.348149+0200 justCare 2[64058:433979] [General] (
    0   CoreFoundation                      0x000000019e1d3154 __exceptionPreprocess + 176
    1   libobjc.A.dylib                     0x000000019dcf24d4 objc_exception_throw + 60
    2   CoreFoundation                      0x000000019e1d2ff8 +[NSException exceptionWithName:reason:userInfo:] + 0
    3   Contacts                            0x00000001b16f23c4 -[CNMutableContact setPhoneNumbers:] + 328
    4   justCare 2                          0x0000000100a04e04 $s10justCare_213ContactsClassC13updateContact7contact8lastName05firstI09birthDate4mail6phone16phone26mobile6street4city8postCode6gruppeySo9CNContactC_S2S10Foundation0L0VSgS8StF + 1752
    5   justCare 2                          0x0000000100e3eb64 $s10justCare_220KlientenAdressenViewV13addToContacts33_94E28232C5F5EF34661BF1E9CA92653DLLyyF + 6208
    6   justCare 2                          0x0000000100e3d314 $s10justCare_220KlientenAdressenViewV4bodyQrvg7SwiftUI05TupleE0VyAE0E0PAEE5sheet11isPresented9onDismiss7contentQrAE7BindingVySbG_yycSgqd__yctAeHRd__lFQOyAE6HStackVyAGyAE6SpacerV_AE6ButtonVyAiEE10labelStyleyQrqd__AE05LabelU0Rd__lFQOyAE0V0VyAE4TextVAE5ImageVG_AE08IconOnlyvU0VQo_GAiEE8disabledyQrSbFQOyA8__Qo_A10_SgAiEE5alert_AK7actionsQrAE18LocalizedStringKeyV_APqd__yXEtAeHRd__lFQOyA10__AGyAWyA1_G_A16_tGQo_AiEE5frame5width6height9alignmentQr12CoreGraphics7CGFloatVSg_A26_AE9AlignmentVtFQOyAE7DividerV_Qo_AiEE4helpyQrA15_FQOyA10__Qo_tGG_AA014AdresseAddEditE0VQo__AiEE0M6Change2of7performQrqd___yqd__ctSQRd__lFQOyAiEEA19_A20_A21_A22_QrA26__A26_A28_tFQOyAE4ListVyAA0cD0CAE7ForEachVyAE14FetchedResultsVyA45_G10Foundation4UUIDVSgAiEE20listRowSeparatorTint_5edgesQrAE5ColorVSg_AE12VerticalEdgeO3SetVtFQOyAiEE16listRowSeparator_A56_QrAE10VisibilityO_A63_tFQOyAiEE7paddingyQrAE4EdgeOA62_V_A26_tFQOyAiEE3tagyQrqd__SHRd__lFQOyASyAGyAiEEA19_A20_A21_A22_QrA26__A26_A28_tFQOyAE6VStackVyAGyAiEEA19_A20_A21_A22_QrA26__A26_A28_tFQOyA1__Qo_Sg_A74_A74_tGG_Qo__A73_yAGyASyAGyA1__AuiEE15foregroundColoryQrA59_FQOyA3__Qo_A80_SgA3_SgtGG_ASyAGyA1__AUA1_SgA85_tGGtGGtGG_A45_Qo__Qo__Qo__Qo_GG_Qo__AA0C0CQo_tGyXEfU_A34_yXEfU_yycfU8_ + 36
    7   SwiftUI                             0x00000001c6e285e4 block_destroy_helper + 1276
    8   SwiftUI                             0x00000001c6e26978 OUTLINED_FUNCTION_1 + 23992
    9   SwiftUI                             0x00000001c6e269dc OUTLINED_FUNCTION_1 + 24092
    10  AppKit                              0x00000001a15218a8 -[NSApplication(NSResponder) sendAction:to:from:] + 440
    11  AppKit                              0x00000001a15216c0 -[NSControl sendAction:to:] + 72
    12  AppKit                              0x00000001a1521604 __26-[NSCell _sendActionFrom:]_block_invoke + 100
    13  AppKit                              0x00000001a152152c -[NSCell _sendActionFrom:] + 204
    14  AppKit                              0x00000001a1521450 -[NSButtonCell _sendActionFrom:] + 88
    15  AppKit                              0x00000001a151ea54 NSControlTrackMouse + 1480
    16  AppKit                              0x00000001a151e460 -[NSCell trackMouse:inRect:ofView:untilMouseUp:] + 144
    17  AppKit                              0x00000001a151e318 -[NSButtonCell trackMouse:inRect:ofView:untilMouseUp:] + 488
    18  AppKit                              0x00000001a151d7e4 -[NSControl mouseDown:] + 448
    19  AppKit                              0x00000001a151c2cc -[NSWindow(NSEventRouting) _handleMouseDownEvent:isDelayedEvent:] + 3476
    20  AppKit                              0x00000001a14a6f08 -[NSWindow(NSEventRouting) _reallySendEvent:isDelayedEvent:] + 364
    21  AppKit                              0x00000001a14a6bc8 -[NSWindow(NSEventRouting) sendEvent:] + 284
    22  AppKit                              0x00000001a14a5f0c -[NSApplication(NSEvent) sendEvent:] + 1556
    23  AppKit                              0x00000001a16f5fc4 -[NSApplication _handleEvent:] + 60
    24  AppKit                              0x00000001a136d368 -[NSApplication run] + 500
    25  AppKit                              0x00000001a1344794 NSApplicationMain + 880
    26  SwiftUI                             0x00000001c643f6b8 OUTLINED_FUNCTION_8 + 8272
    27  SwiftUI                             0x00000001c75a05ac OUTLINED_FUNCTION_14 + 188
    28  SwiftUI                             0x00000001c6e20c48 OUTLINED_FUNCTION_1 + 136
    29  justCare 2                          0x0000000101365e9c $s10justCare_20aB5_2AppV5$mainyyFZ + 40
    30  justCare 2                          0x000000010136619c main + 12
    31  dyld                                0x000000019dd23f28 start + 2236
)

Why isn't the contact updated? I would be very thankful for help.


Solution

  • The error seems to be around modifying the contact's phone number based on the stack trace.

    3 Contacts 0x00000001b16f23c4 -[CNMutableContact setPhoneNumbers:] + 328

    There's also the message:

    A property was not requested when contact was fetched.

    Looking at your code, your checkForExistingContact method only fetches the attributes CNContactGivenNameKey, CNContactFamilyNameKey, and CNContactEmailAddressesKey. The phone number key is not listed.

    There is also this note in the documentation for CNMutableContact:

    You may modify only those properties whose values you fetched from the contacts database. When fetching a contact, you specify which properties you want to retrieve from the database. The contact store then populates the properties of a CNContact object with those values. After creating a mutable copy of that object, you can modify only those properties for which a value exists. If you attempt to access a property that is not available, the CNMutableContact object throws a CNContactPropertyNotFetchedExceptionName exception.

    You need to update the line:

    let keysToFetch = [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactEmailAddressesKey] as [CNKeyDescriptor]
    

    to include all possible keys you will try to update in your updateContact method.