Implement search on all values of CNContact object - iphone

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.

Related

Having a hard time trying to filter out the hits in my Algolia InstantSearch searchController

My goal is to filter out hits by schoolID. School users on my app have unique schoolIDs and I want them to be able to search through events that only they created and not see any other school's events. I’ve been wanting to figure this out ever since I got the Algolia implemented into my app and I just can’t wrap my head around it, the concept seems so easy to explain but when i try to implement it, there is literally no change in my search results.
I copied the code that one of the Algolia team members in the community forum replied with, but it still doesn’t filter out the hits by schoolID, I will attach my block of code in the updateSearchResults() method, please point out any mistakes you see in my code, I’ve been gunning to fix this big issue in my app for a while. Thanks in advance.
func updateSearchResults(for searchController: UISearchController) {
let searchBar = searchController.searchBar
let settings = Settings()
.set(\.searchableAttributes, to: [.default("eventName")])
.set(\.attributesForFaceting, to: [.filterOnly("schoolID")])
.set(\.attributesToRetrieve, to: ["*"])
searchResultsIndex.setSettings(settings) { (result) in
if case .success(let response) = result {
print("Response: \(response.wrapped)")
}
}
getTheSchoolsID { (schoolID) in
if let id = schoolID {
let schoolID: String = id
}
let query = Query(searchBar.searchTextField.text!).set(\.filters, to: "schoolID:\(schoolID)")
searchResultsIndex.search(query: query) { (result) in
if case .success(let response) = result {
print("Query Success!")
}
}
}
}

How to make app automatically recognise current user Firestore document ID when signed in?

When a user is signed up through my form, a document gets created associated with that user. My main goal is to create a global function that can recognize the user that is signed in and get their document ID. I have a function setup for adding documents to a subcollection of the user document which is perfectly setup, the only downfall is that when I'm testing with multiple accounts, I have to manually switch the collection path. Here is what I mean.
#IBAction public func createEventButton(_ sender: UIButton) {
let error = validateFields()
if error != nil {
showError(error!)
} else {
db.collection("school_users/\(stThomas)/events").addDocument(data: ["event_name": nameTextField.text, "event_date": dateTextField.text, "event_cost": costTextField.text, "for_grades": gradesTextField.text]) { (error) in
if error != nil {
self.showError("There was an error trying to add user data to the system.")
} else {
self.dismiss(animated: true, completion: nil)
}
}
}
So as you can see here, I am using string interpolation with the "stThomas" constant I used to store a document ID. I basically want to create a function that will recognize the document ID of the user signed in so I can use my Constants instead of string interpolation and having to manually switch the user collection path each time, which would be eventually impossible during production.
Not to mention, I do have a function to grab the document ID, say for instance an event is clicked, but as a beginner in Swift, I can't seem to connect the dots. I will also show this function for clarification.
func getDocID() {
db.collection("school_users/\(notreDame)/events").getDocuments() { (querySnapshot, error) in
if let error = error {
print("There was an error getting the documents: \(error)")
} else {
self.documentsID = querySnapshot!.documents.map { document in
return DocID(docID: (document.documentID))
}
self.tableView.reloadData()
}
}
}
And in this function you can see my other constant "notreDame" with another stored document ID. If anybody knows a simple way to do this that would be great. And yes, I checked the Firebase documents, thank you for asking.
I've did some extra research and realized that I can use User IDs in collection paths. My problem is now solved. Many more problems to come though.

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)
}

How do you store a dictionary on Parse using swift?

I am very new to swift and I don't know Obj C at all so many of the resources are hard to understand. Basically I'm trying to populate the dictionary with PFUsers from my query and then set PFUser["friends"] to this dictionary. Simply put I want a friends list in my PFUser class, where each friend is a PFUser and a string.
Thanks!
var user = PFUser()
var friendsPFUser:[PFUser] = []
var friendListDict: [PFUser:String] = Dictionary()
var query = PFUser.query()
query!.findObjectsInBackgroundWithBlock {
(users: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
// The find succeeded.
println("Successfully retrieved \(users!.count) users.")
// Do something with the found objects
if let users = users as? [PFUser] {
friendsPFUser = users
for user in friendsPFUser{
friendListDict[user] = "confirmed"
}
user["friends"] = friendListDict //this line breaks things
user.saveInBackground()
}
} else {
// Log details of the failure
println("Error: \(error!) \(error!.userInfo!)")
}
}
To be clear, this code compiles but when I add
user["friends"] = friendListDict
my app crashes.
For those who might have this issues with. "NSInternalInconsistencyException" with reason "PFObject contains container item that isn't cached."
Adding Objects to a user (such as arrays or dictionaries) for security reasons on Parse, the user for such field that will be modified must be the current user.
Try signing up and using addObject inside the block and don't forget do save it!
It helped for a similar problem I had.

iPhone: Create Custom Field in contacts

i am developing a application in which i add contact to contact List. i am able to basic info like name, address, email, phone, notes etc. but i want to add some custom field like userLabel1, userValue1 , bioPersonal, bioWork, bioOther. so i want to add custom fields to address Book'contacts.
whether it is possible to add custom field to contact? if yes , then please suggest any link or sample code?
Basically, there is no way to do that. Address Book doesn't let you add custom fields. However, what you can do is put your data in the "Notes" field for each contact. That will, however, look weird in apps other than yours.
let store = CNContactStore()
// phoneNumberToEdit is the CNContact which you need to update
guard var mutableCont = phoneNumberToEdit?.mutableCopy() as? CNMutableContact else { return }
let phoneNumber = CNPhoneNumber(stringValue: "0123456789")
var currentContact = CNLabeledValue(label: "MyCustomLabel", value: phoneNumber)
mutableCont.phoneNumbers = [currentContact]
let request2 = CNSaveRequest()
request2.update(mutableCont)
do{
try store.execute(request2)
} catch let error{
print(error)
}