The following example is working to access the CNContactStore and list all contacts with phone numbers and/or eMail addresses. I have tested the example successfully in these MacOS versions:
- 10.11.6 'El Capitan'
- 10.12.6 'Sierra'
- 10.13.6 'High Sierra'
- 10.14.6 'Mojave'
- 10.15.7 'Catalina'
- 11.7.10 'Big Sur'
- 12.7.6 'Monterey'
- 13.7.1 'Ventura'
- 14.7.1 'Sonoma'
- 15.1.1 'Sequoia'
In MacOS 10.10.5 'Yosemite' this example doesn't work because the contacts framework was introduced in 'El Capitan'.
It took me many many hours to program a working example in PureBasic. I programmed more than 40 not working test programs in PureBasic and even a dozen test programs in Objective C and Swift which sometimes kind of worked but also displayed several severe warnings. Because of Apple's security settings (which also became more strict with newer MacOS versions) this was a minefield and I paused several times because I didn't seem to come any further. As a last resort in my despair I tried to find help by opening a session with ChatGPT 4o mini and against all odds I was able to find a first PureBasic example which finally worked. Of course the examples proposed by ChatGPT didn't work but by asking ChatGPT to correct the errors, to use CocoaMessage() instead of importing API calls and adding other crucial code lines by myself after about 20 minutes of cooperative work I unbelievably had a first working example!
In order to work with contacts, the program has to ask the user to allow access to the AddressBook. To test the sourcecode in the PureBasic IDE, the program in the IDE also has to ask for this allowance in an automatic requester (by the OS). In Yosemite to Ventura this should work. In Sonoma and Sequoia this didn't work. After compiling the example code to an app, the started app also should display the requester for allowance to access the AddressBook. This worked relatively reliably for Yosemite to Sequioa.
For obtaining the OS access request in the IDE on El Capitan to Sierra it was possible to execute my example code in PB 6.04. In Big Sur to Ventura I had to run the example code in PB 5.73 (PB 6.04 or 6.12 don't trigger the requester). In Sonoma to Sequoia I was not able to trigger the access request in the IDE at all because PB 5.73 doesn't run anymore on Sonoma and Sequoia and PB 6.04 and 6.12 don't trigger the access requester.
When having problems with the access to the AddressBook you may take a look into the MacOS securty settings:
System settings > Privacy & Security > Contacts
(or in German:
Systemeinstellungen > Datenschutz & Sicherheit > Kontakte)
You are able to activate or deactivate each app by using a switch in the contacts list. But you are not able to delete a program entry. To delete an entry you have to open a console and use the
tccutil console program:
Code: Select all
- Remove all apps for the category Contacts:
> tccutil reset AddressBook
- Remove single app (for example PureBasic IDE) from Contacts:
- Get Bundle-ID of PureBasic IDE (will return com.fantaisiesoftware.purebasicide):
> osascript -e 'id of app"PureBASIC-6.11-x64"'
- Remove PureBasic IDE from Contacts:
> tccutil reset AddressBook com.fantaisiesoftware.purebasicide
The app name
PureBASIC-6.11-x64 may be different in your case because it depends on which has been the first PureBasic version you installed on your MacOS. But all PureBasic versions work with this registered version name. They all result in the AppID
com.fantaisiesoftware.purebasicide. So for the PureBasic IDE the osascript step wouldn't be necessary.
Code: Select all
EnableExplicit
#KeyList = "givenName+familyName+phoneNumbers+emailAddresses"
ImportC "-framework Contacts"
EndImport
Define i.I
Define Info.S
Define Result.S
NewList Key.S()
Procedure.S GetContactsFromAddressBook(KeyList.S)
Protected ClassName.S
Protected Contact.I
Protected ContactArray.I
Protected ContactCount.I
Protected ContactInfo.S
Protected ContactStore.I
Protected Error.S
Protected i.I
Protected Item.I
Protected ItemCount.I
Protected ItemArray.I
Protected j.I
Protected k.I
Protected Key.S
Protected KeyArray.I
Protected KeyCount.I
ContactStore = CocoaMessage(0, 0, "CNContactStore new")
If ContactStore = 0
ProcedureReturn "ERROR: Failed to create CNContactStore instance!"
EndIf
KeyCount = CountString(KeyList, "+") + 1
; ----- Create mutable array with 1st key name
Key = StringField(KeyList, 1, "+")
KeyArray = CocoaMessage(0, 0, "NSMutableArray arrayWithObject:$", @Key)
If KeyCount > 1
; ----- Add keys 2 to n to mutable array
For i = 2 To KeyCount
Key = StringField(KeyList, i, "+")
CocoaMessage(0, KeyArray, "addObject:$", @Key)
Next i
EndIf
; ----- Fetch all unified contacts containing the keys in key array
ContactArray = CocoaMessage(0, ContactStore,
"unifiedContactsMatchingPredicate:", 0,
"keysToFetch:", KeyArray,
"error:", @Error)
If ContactArray = 0
CocoaMessage(0, ContactStore, "release")
ProcedureReturn "ERROR: Fetching of the contacts failed!"
EndIf
ContactCount = CocoaMessage(0, ContactArray, "count")
ContactInfo = "Contacts found: " + ContactCount + #CR$ + #CR$
If ContactCount > 0
For k = 1 To ContactCount
Contact = CocoaMessage(0, ContactArray, "objectAtIndex:", k - 1)
ContactInfo + "Contact " + Str(k) + #CR$
For j = 1 To KeyCount
Key = StringField(KeyList, j, "+")
Item = CocoaMessage(0, Contact, Key)
ClassName = PeekS(CocoaMessage(0, CocoaMessage(0, Item, "className"),
"UTF8String"), -1, #PB_UTF8)
; ----- Check whether the returned item is an array
If FindString(ClassName, "Array") = 0
ContactInfo + Key + ": " + PeekS(CocoaMessage(0, CocoaMessage(0,
Contact, Key), "UTF8String"), -1, #PB_UTF8) + #CR$
Else
ItemArray = Item
ItemCount = CocoaMessage(0, ItemArray, "count")
ContactInfo + Key + ":" + #CR$
For i = 1 To ItemCount
Item = CocoaMessage(0, ItemArray, "objectAtIndex:", i - 1)
If FindString("phoneNumbers", Key)
ContactInfo + "- " + PeekS(CocoaMessage(0, CocoaMessage(0,
CocoaMessage(0, Item, "value"), "stringValue"), "UTF8String"),
-1, #PB_UTF8) + #CR$
ElseIf FindString("emailAddresses", Key)
ContactInfo + "- " + PeekS(CocoaMessage(0, CocoaMessage(0,
Item, "value"), "UTF8String"), -1, #PB_UTF8) + #CR$
Else
CocoaMessage(0, ContactStore, "release")
ProcedureReturn "ERROR: Array evaluation for key " + Key +
"is currently not implemented!"
EndIf
Next i
EndIf
Next j
ContactInfo + #CR$
Next k
EndIf
; ----- Release the CNContactStore object
CocoaMessage(0, contactStore, "release")
ProcedureReturn ContactInfo
EndProcedure
Result = GetContactsFromAddressBook(#KeyList)
If Left(Result, 5) = "ERROR"
MessageRequester("ERROR", Mid(Result, 7))
End
EndIf
If Result = ""
Info = "No Contacts were found!"
Else
Info = Result
EndIf
OpenWindow(0, 0, 0, 270, 300, "Address book contacts",
#PB_Window_SystemMenu | #PB_Window_ScreenCentered)
EditorGadget(0, 5, 5, WindowWidth(0) - 10, WindowHeight(0) - 10)
AddGadgetItem(0, 0, Info)
Repeat
Until WaitWindowEvent() = #PB_Event_CloseWindow