3

I have seen many SO question curious about this case but still I am posting this as many of developers out there may also want to know this another reason is that no solution is working for me .

I have used following code but it only works when My app is in background. but I am not notified when my app is killed and meanwhile user has updated the info of any contact. So in this case I am not sure how to do it.

What I am doing: here is a code snippet what I am trying to do

From iOS 9 you can register your class to observe CNContactStoreDidChangeNotification

NSNotificationCenter.defaultCenter().addObserver(
self,
selector: #selector(addressBookDidChange),
name: NSNotification.Name.CNContactStoreDidChange,
object: nil)

And then:

@objc func addressBookDidChange(notification: NSNotification){     
//Handle event here...
}

I found this solution over here:

Whats Happening: Through this way I am able to get my app notified once the user has updated his contact while app is in background.

What I want: I just want to know that if the user has updated any contact even though my app was killed then How to get my app notified with updated contacts?

Please let me know if you have solution of this issue in advance.

UPDATE: I have seen Whatsapp doing this. Is there anyone who can tell me how Whatsapp is doing this?

Zaphod
  • 6,758
  • 3
  • 40
  • 60
A.s.ALI
  • 1,992
  • 3
  • 22
  • 54
  • It's not possible to check updates if your app has been killed. How whatsapp does it maybe by running a check when the app loads – Keshu R. Jan 07 '20 at 10:49
  • but that is too fast, I dont know how they are checking and replacing updated contacts that fast – A.s.ALI Jan 07 '20 at 10:53
  • maybe they dont need to update the contacts, they just load the contact list directly from Contacts in phone. I know another company which does something similar and it is very fast unless there are thousands of contacts to load – Scriptable Jan 07 '20 at 10:57
  • can you please elaborate more over this? – A.s.ALI Jan 07 '20 at 11:00
  • Do you know about VoiP ? Whatsapp uses this to update contacts when needed. – Aqeel Ahmad Jan 07 '20 at 11:46
  • Whenever we open whatsapp on web it requires us to connect our phone with the working internet. they send the VoiP to the mobile and verify if the phone is connected to the internet. meanwhile they get all the contact list. – Aqeel Ahmad Jan 07 '20 at 11:47
  • no, what is it? as far as I know, Voice over IP is VoIP , NVM if this VoIP is anything else, please share something on it – A.s.ALI Jan 07 '20 at 11:48
  • @AqeelAhmad I think you got it wrong, Suppose the user has a contact with the name of ADAM, and his chat head in whats app shows same name, however the ADAM account on whats app could have name of ADAM LEVENE , so the user will always get the name ADAM LEVENE as ADAM as he has this name in his contact list. Now Suppose he edits the name and save it ias aadam he will further on will see same chat head with name of aadam – A.s.ALI Jan 07 '20 at 11:50
  • Can you explain a little bit more with an example? I didn't understand what you said in your last comment. give me example in terms of A and B – Aqeel Ahmad Jan 07 '20 at 12:02
  • @A.s.ALI And I'm sure that the only way to access the ios app in killed mode is VoiP. I'm afraid you can't observe this thing while contact is getting update. The only way to do that is to send voip notification to the device to get any information. – Aqeel Ahmad Jan 07 '20 at 12:07
  • https://developer.apple.com/library/archive/documentation/Performance/Conceptual/EnergyGuide-iOS/OptimizeVoIP.html#//apple_ref/doc/uid/TP40015243-CH30 you can read voip from here – Aqeel Ahmad Jan 07 '20 at 12:09
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/205516/discussion-between-aqeel-ahmad-and-a-s-ali). – Aqeel Ahmad Jan 07 '20 at 12:16
  • @AqeelAhmad what is your main idea, by using VOIP ? – A.s.ALI Jan 08 '20 at 04:55

3 Answers3

3

To check if a contact has changed you can use a custom hash function because the native one only checks for the identifier:

extension CNContact {
    var customHash : Int {
        var hasher = Hasher()

        hasher.combine(identifier)
        hasher.combine(contactType)
        hasher.combine(namePrefix)
        hasher.combine(givenName)
        hasher.combine(middleName)
        hasher.combine(familyName)
        hasher.combine(previousFamilyName)
        hasher.combine(nameSuffix)
        hasher.combine(nickname)
        hasher.combine(organizationName)
        hasher.combine(departmentName)
        hasher.combine(jobTitle)
        hasher.combine(phoneticGivenName)
        hasher.combine(phoneticMiddleName)
        hasher.combine(phoneticFamilyName)
        if #available(iOS 10.0, *) {
            hasher.combine(phoneticOrganizationName)
        }
        hasher.combine(note)
        hasher.combine(imageData)
        hasher.combine(thumbnailImageData)
        if #available(iOS 9.0, *) {
            hasher.combine(imageDataAvailable)
        }
        hasher.combine(phoneNumbers)
        hasher.combine(emailAddresses)
        hasher.combine(postalAddresses)
        hasher.combine(urlAddresses)
        hasher.combine(contactRelations)
        hasher.combine(socialProfiles)
        hasher.combine(instantMessageAddresses)
        hasher.combine(birthday)
        hasher.combine(nonGregorianBirthday)
        hasher.combine(dates)

        return hasher.finalize()
    }
}

(You can remove fields you don't care)

Then you have to keep a dictionary inside your app to store the hash values of all the contacts, to build it just do:

let hashedContacts = [String:Int]()

for contact in allContacts {
    hashedContacts[contact.identifier] = contact.customHash
}

You have to store it on the file system.

Whenever a contact is updated, you update it:

hashedContacts[updatedContact.identifier] = updatedContact.customHash

Then at every launch, you load the saved dictionary, and you check for differences:

for contact in allContacts {
    if contact.customHash != savedHashedValues[contact.identifier] {
        // This contact has changed since last launch 
        ...
    }
}

And voilà!

EDIT:

How to save the hash map on disk...

var hashedContacts = ...

guard let name = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first?.appendingPathComponent("hashedContacts")
    else { return }

try? (hashedContacts as NSDictionary).write(to: name)

How to load the hash map from disk...

guard 
    let name = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first?.appendingPathComponent("hashedContacts"),
    let loadedContacts = (try? NSDictionary(contentsOf: name, error: ())) as? [String:Int]
    else { return }

// Do whatever you want with loaded contacts...
Zaphod
  • 6,758
  • 3
  • 40
  • 60
  • looks interested, but I do not know how it is going to be used – A.s.ALI Jan 09 '20 at 10:39
  • How is it going to be used by who? – Zaphod Jan 09 '20 at 10:44
  • give some example on how to do it – A.s.ALI Jan 09 '20 at 10:50
  • Hum... That's what I just did... The only part I did not write is the save on the file system and the retrieval of all contacts, but it is not the point of this question. – Zaphod Jan 09 '20 at 10:56
  • i know, I am able to fetch all contact from device, now please tell me how to use this code and how to assemble it – A.s.ALI Jan 09 '20 at 11:01
  • I can't program for you, please ask what part of the explanation you don't understand, and I'll try to help, because you are here to find solutions to your problems, not to have your program written by others. – Zaphod Jan 09 '20 at 11:21
  • i did not get this part.... Whenever a contact is updated, you update it: hashedContacts[updatedContact.identifier] = updatedContact.customHash – A.s.ALI Jan 27 '20 at 05:34
  • I meant: you update its hashed value. Whenever you are informed a modification has been made to a contact, you need to keep your hash values updated. Otherwise you'll have the wrong hash value in your dictionary. Remember that the hash value represents, within a single integer, the state of a contact. Whenever you change something (name, phone, etc.) the hash value changes. That's why you need to keep it updated, for it to reflect the state of the contact. Is that clearer ? – Zaphod Jan 27 '20 at 08:42
  • my question was how to get notified that when the user has changed any of its contact info. And as you said in your comment "Whenever you are informed a modification has been made to a contact," this is my main problem man. I mean How i can get notified that contacts are changed? – A.s.ALI Jan 27 '20 at 10:05
  • You have to split this question in three : 1. The first time you launch the app, you build the dictionary with the hash values and save it on the file system. 2. When your app receives `CNContactStoreDidChange` you update your dictionary with the new contact hash value, and you save it on the file system. 3. When the app is launched (that is when it was killed and it can't receive the `CNContactStoreDidChange` notification) you have to load the dictionary you saved on file, check hash values for every contacts and if some are different that means contact changed. Then you save the new values. – Zaphod Jan 27 '20 at 13:46
  • no...............Or may be I have not implemented it correctly – A.s.ALI Feb 03 '20 at 06:13
  • also thing is how will my app will be notified that there are new changes in Contacts, so to run the contact checker code, once the user ran my app after couple of days – A.s.ALI Feb 03 '20 at 06:14
  • Oh... I think we misunderstood your problem. Once you app is killed, there is NO WAY for it to be notified by a contact change. The app itself can't be notified once killed. Period. What we propose here is for your app to detect, the next time it's launched, what contacts have changed since the last time it was up... That's it. You save a "picture" of your contacts, while the app is running, every change is saved on this "picture." The next time you launch it, you compare those pictures to deduce what contacts have changed... That's it. But you can't launch you app on a contact change... – Zaphod Feb 03 '20 at 09:33
  • The picture is you dictionary `hashedContacts`. What is your question : How to save it on disk? I edited my answer for it... – Zaphod Feb 04 '20 at 08:15
  • I appreciate your helping behaviour here but Can you send me working code please – A.s.ALI Feb 04 '20 at 10:51
  • 1
    @Zaphod Your patience here was admirable :) Anyways, I found it easier to just use `CNContactVCardSerialization` to transform the contact into a vcard and then hash the entire vcard. This prevents me from needing to deal with individual contact fields as you did. – Gavin Wright Nov 02 '22 at 18:27
  • @GavinWright thanks, indeed, it's an easier alternative! – Zaphod Nov 03 '22 at 08:43
0

Whenever you open your app you need to get all the contacts from the contact list and can compare to previous one which is saved inside of your app. After that you can push your contact list to server.

Aqeel Ahmad
  • 709
  • 7
  • 20
  • how the server will know if the user has updated the contacts or not? – A.s.ALI Jan 08 '20 at 04:57
  • there are some limitations. server can not remain updated all the time. the thing is whenever you need the contact information of the user, you will have to send a VoiP notification to that device to update your server data. As @Scriptable said in above comments that you only need to get all the contact list from the mobile whenever needed. – Aqeel Ahmad Jan 08 '20 at 10:00
  • ok, so its mean that I have to fetch All contact from device and the then I can push them to server. However If contacts are like upto 2000 then I have seen a massive delay in fetching the contacts. But I did not see the same delay in whats app. for example I have 2000 contacts and on I change the name of 1 contact. When I open the whatsapp I see the old name is still there and mean while with in 1 seconds its gets updated to new contact. I saw there is no delay in whats app. – A.s.ALI Jan 08 '20 at 11:27
  • well, you can work on your algorithm like you don't need to send 2k contacts to the server every time. you can save contacts inside of your app. When you will send a voip notification to your app you can find out all the changes then you can push all the updated contacts. – Aqeel Ahmad Jan 08 '20 at 11:30
  • I found no use of VOIP in my app, as I wanted to make sure that whenever user will open the app, he may look updated name in my app of the contact that he previously updated at any time. It does not matter my app was killed or was in background – A.s.ALI Jan 08 '20 at 11:57
  • I think there is a misunderstanding regarding your question. I was answering you if you want to update your contact list on your web server. According to your previous comment it seems like you only need it inside of your app. – Aqeel Ahmad Jan 08 '20 at 12:01
  • 1
    yes i need to know in my app that some contacts are updated, if yes then I am going to update it on my server. There is nothing i want to do on server to send some sort of Notification to wake up app and to update contacts of user on service. I just want that when my app is opened by user , my app should get some knowledge about updated contacts and those contacts must be sync with the server – A.s.ALI Jan 08 '20 at 12:46
  • @A.s.ALI got it. Would you like to edit my answer or should I update it by myself? – Aqeel Ahmad Jan 08 '20 at 12:53
  • 1
    Sending VOIP notifications to update your app on the background is no longer allowed on iOS 13. For apps built against ios 13 sdk and newer, PushKit requires you to use CallKit when handling VoIP notifications. https://developer.apple.com/documentation/pushkit/responding_to_voip_notifications_from_pushkit – johnny Jan 08 '20 at 18:41
-2

What you can do is send an update notification to your application on launch screen. This might have an illusion to your user that you have done the changes while in background.

CoderSulemani
  • 123
  • 11