Company field when enumerating contacts through CNContactStore - swift

I'm fetching some contact fields using CNContactStore such as first name (CNContactGivenNameKey) and last name (CNContactFamilyNameKey) but I can't find the key for company.
let request = CNContactFetchRequest(keysToFetch: [
CNContactGivenNameKey as NSString,
CNContactFamilyNameKey as NSString,
])
request.sortOrder = .familyName
do {
let store = CNContactStore()
try store.enumerateContacts(with: request) { contact, stop in
// ...
}
} catch {
print(error)
}

The key is CNContactOrganizationNameKey.
The confusion comes from the fact that the Contacts app (in all iOS, iPadOS and macOS) use the word company while the key refers to it as organization, making it hard to find through auto-complete in Xcode, or when searching for it in Google or Stack Overflow.

Related

Swift OSX CNContact.organizationName crash in High Sierra

My OSX app allows the user to select a contact from their contacts list and loads the details into a Customer record. I am using CNContactPicker to retrieve a contact into a CNContact record. One of the fields I need to retrieve is organizationName. This works perfectly in OS's prior to High Sierra, but upon upgrading to High Sierra it will crash. All other CNContact fields can be retrieved with no issue (e.g. names, email, address etc). I do have permissions requested in my info.plist file.
It makes no difference if the Contact does/does not have an Organization Name.
Not much to show in terms of code:
// This fails on 1st line - any reference to organizationName causes failure
if (contact.organizationName != "") {
self.name = contact.organizationName
}
// This works
if (contact.givenName != "") {
self.name = contact.givenName
}
// This works
if (contact.contactType == CNContactType.organization) {
// Do something
}
The actual error is: [General] A property was not requested when contact was fetched.
I would like to know what has changed in the OS to cause this error, and if there is a solution or workaround please.
I submitted a bug report with Apple and received the following response which fixes my issue. Essentially, even though I have retrieved a Contact that the user selected, I need to do a CNContactFetchRequest to fetch this specific contact again (using the identifier) with keys specified (e.g. organisation).
Here is their exact response:
If you want to make sure organizationName is available, execute a CNFetchRequest for a contact with the same identifier (as returned from CNContactPicker delegate method) and provide a set of keys to fetch containing CNContactOrganizationName.
Here is the code:
var validContacts: [CNContact] = []
let contactStore = CNContactStore()
do {
// Specify the key fields that you want to be fetched.
// Note: if you didn't specify your specific field request. your app will crash
let fetchRequest = CNContactFetchRequest(keysToFetch: [CNContactOrganizationNameKey as CNKeyDescriptor])
fetchRequest.predicate = CNContact.predicateForContacts(withIdentifiers: [contact.identifier])
try contactStore.enumerateContacts(with: fetchRequest, usingBlock: { (contact, error) -> Void in
validContacts.append(contact)
})
for validContact in validContacts {
// Do something with your contact, there should be only one.
}
} catch let e as NSError {
print(e)
}

CNContactVCardSerialization.data(with:) always returns nil result

I am trying to retrieve all contacts and save them in VCard form (swift 4, XCode 9.0). But CNContactVCardSerialization.data(with:) always returns nil. Here is my code:
var contacts = [CNContact]()
let request = CNContactFetchRequest(keysToFetch:[CNContact.descriptorForAllComparatorKeys()])
do {
try contactsStore.enumerateContacts(with: request, usingBlock:
{ (contact:CNContact, result:UnsafeMutablePointer<ObjCBool>) in
self.contacts.append(contact)
})
}
catch {
}
// at this point all contacts are in the "contacts" array.
var data = Data()
do {
try data = CNContactVCardSerialization.data(with: contacts)
}
catch {
print("some error in contacts:" + String(describing: error));
}
print(">>>data:" + String(data.count))
Output:
2017-11-02 XXX [5224:449081]
Exception writing contacts to vCard (data): A property was not
requested when contact was fetched.
2017-11-02 XXX [5224:449362] XPC
connection interrupted
some error in contacts:nilError
>>>data:0
I red the question below but it does not help.
How to use method dataWithContacts in CNContactVCardSerialization?
I added "Privacy - Contacts Usage Description" into info.plist
Maybe you need to provide some specific keys to fetch?
UPD: Yep, if you want to fetch requests and serialize them, you have to set keys to fetch:
keysToFetch:#[[CNContactVCardSerialization descriptorForRequiredKeys]]
Change
let request = CNContactFetchRequest(keysToFetch:[CNContact.descriptorForAllComparatorKeys()])
To
let request = CNContactFetchRequest(keysToFetch:[CNContactVCardSerialization.descriptorForRequiredKeys()])

Matching CNcontact and Digits Find Friends Swift 3

I am trying to build iPhone App with digits Find a friend feature
I can get list of matching digitUserID from Digits.
Now I am struggling to match UserID and CNContacts.
Please point any examples to deal this.
As update:
do
{
try contactStore.enumerateContactsWithFetchRequest(CNContactFetchRequest(keysToFetch: [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactMiddleNameKey, CNContactEmailAddressesKey,CNContactPhoneNumbersKey])) {
(contact, cursor) -> Void in
self.results.append(contact)
}
}
catch{
print("Handle the error please")
}
The above I have managed to get all contact but I don't know how to pass a phone number filter into this and get exact contact match with CNContact
Ideally, one would have expected predicate of the CNContactFetchRequest to do the job, but that (still; argh) only accepts a narrow list of predicates defined with CNContact (e.g. CNContact predicateForContacts(matchingName:) or predicateForContacts(withIdentifiers:). It doesn't even accept the block-based NSPredicate.
So, you have to enumerate through, looking for matches yourself, e.g.
let request = CNContactFetchRequest(keysToFetch: [
CNContactGivenNameKey as CNKeyDescriptor,
CNContactFamilyNameKey as CNKeyDescriptor,
CNContactMiddleNameKey as CNKeyDescriptor,
CNContactEmailAddressesKey as CNKeyDescriptor,
CNContactPhoneNumbersKey as CNKeyDescriptor
])
do {
try contactStore.enumerateContacts(with: request) { contact, stop in
for phone in contact.phoneNumbers {
// look at `phone.value.stringValue`, e.g.
let phoneNumberDigits = String(phone.value.stringValue.characters.filter { String($0).rangeOfCharacter(from: CharacterSet.decimalDigits) != nil })
if phoneNumberDigits == "8885551212" {
self.results.append(contact)
return
}
}
}
} catch let enumerateError {
print(enumerateError.localizedDescription)
}
Regarding matching "digit UserID", I don't know what that identifier is (is it a Contacts framework identifier or Digits' own identifier?).

Implement search on all values of CNContact object

I want to implement search on all contacts fetched on a table view using ios9, CNContacts framework. I can search through GivenName, FamilyName etc, but want to search through phone numbers and emails as well when a user enters a search query in search bar. Just as we can search phone numbers/emails in ios9 Apple contacts.
I know, since phonenumbers/emailsaddresses are array of tuples, it is not possible to do it using NSPredicate format string.
Here is what you could do
func findAllEmails () {
let fetch = CNContactFetchRequest(keysToFetch: [CNContactEmailAddressesKey])
fetch.keysToFetch = [CNContactEmailAddressesKey] // Enter the keys for the values you want to fetch here
fetch.unifyResults = true
fetch.predicate = nil // Set this to nil will give you all Contacts
do {
_ = try contactStore.enumerateContactsWithFetchRequest(fetch, usingBlock: {
contact, cursor in
// Do something with the result...for example put it in an array of CNContacts as shown below
let theContact = contact as CNContact
self.myContacts.append(theContact)
})
} catch {
print("Error")
}
self.readContacts() // Run the function to do something with the values
}
func readContacts () {
print(myContacts.count)
for el in myContacts {
for ele in el.emailAddresses {
// At this point you have only the email Addresses from all your contacts - now you can do your search, put it in an TableView or whatever you want to do....
print(ele.value)
}
}
}
I am sure this code can be optimized ;) I just made it quick...
I tested this solution only with Emails but it should work as well with Phone Numbers. It is up to you what you do with the retrieved values. This might be only a workaround but as you said, you can't do it with a NSPredicate.

Any way to get the name of iPhone user?

Outside of asking the user to input their name, is there any way to get it off the device?
I tried this library, which attempts to extract the name from [UIDevice currentDevice] name], but that doesn't work in a lot of situations:
https://github.com/tiboll/TLLNameFromDevice
Is the user's name present in the phonebook or anywhere else that we have access to in iOS 6?
Well you could go through all the contacts in the AddressBook and see if any of them are marked with the owner flag.
Just be aware that doing this will popup the "this app wants access to the address book" message. Also Apple isn't very keen on these kind of things. In the app review guide it is specified that an app can not use personal information without the user's permission.
You could use Square's solution:
Get the device's name (e.g. "John Smith's iPhone").
Go through the contacts on the phone and look for a contact named "John Smith".
JBDeviceOwner and ABGetMe will both do this for you.
You could use CloudKit. Following a snippet in Swift (ignoring errors):
let container = CKContainer.defaultContainer()
container.fetchUserRecordIDWithCompletionHandler(
{
(recordID, error) in
container.requestApplicationPermission(
.PermissionUserDiscoverability,
{
(status, error2) in
if (status == CKApplicationPermissionStatus.Granted)
{
container.discoverUserInfoWithUserRecordID(
recordID,
completionHandler:
{
(info, error3) in
println("\(info.firstName) \(info.lastName)")
}
)
}
}
)
}
)
The above code was based on the code at http://www.snip2code.com/Snippet/109633/CloudKit-User-Info
to save folks time. in swift4:
let container = CKContainer.default()
container.fetchUserRecordID(
completionHandler: {
(recordID, error) in
guard let recordID = recordID else {
return
}
container.requestApplicationPermission(
.userDiscoverability,
completionHandler: {
(status, error2) in
if (status == CKContainer_Application_PermissionStatus.granted)
{
if #available(iOS 10.0, *) {
container.discoverUserIdentity(withUserRecordID:
recordID,
completionHandler:
{
(info, error3) in
guard let info = info else {
return
}
print("\(info.firstName) \(info.lastName)")
}
)
}
}
}
)
}
)
however: CKUserIdentity no longer exposes either first or last name
So this answer no longer works.
You can use:
NSLog(#"user == %#",[[[NSHost currentHost] names] objectAtIndex:0]);
I did receive compiler warnings that the methods +currentHost and -names were not found. Given the warning, I’m not sure of Apple’s intention to make this available (or not) as a publicly accessible API, however, everything seemed to work as expected without the need to include any additional header files or linking in additional libraries/frameworks.
Edit 1:
You may also take a look at this Link
Edit 2:
If you have integrated your app with Facebook you can easily retrieve the user info, see Facebook Fetch User Data
For SWIFT you can use
NSUserName() returns the logon name of the current user.
func NSUserName() -> String