Find duplicate contacts in Contacts Framework - ios
In Swift 3, I use the new Contact Framework to manipulate contacts, but I don't have any solution for fetching duplicate contacts.
Any idea how to achieve this?
You can do something like this:
/// Find Duplicates Contacts In Given Contacts Array
func findDuplicateContacts(Contacts contacts : [CNContact], completionHandler : #escaping (_ result : [Array<CNContact>]) -> ()){
let arrfullNames : [String?] = contacts.map{CNContactFormatter.string(from: $0, style: .fullName)}
var contactGroupedByDuplicated : [Array<CNContact>] = [Array<CNContact>]()
if let fullNames : [String] = arrfullNames as? [String]{
let uniqueArray = Array(Set(fullNames))
var contactGroupedByUnique = [Array<CNContact>]()
for fullName in uniqueArray {
let group = contacts.filter {
CNContactFormatter.string(from: $0, style: .fullName) == fullName
}
contactGroupedByUnique.append(group)
}
for items in contactGroupedByUnique{
if items.count > 1 {
contactGroupedByDuplicated.append(items)
}
}
}
completionHandler(contactGroupedByDuplicated)
}
I'd build a dictionary keyed by name, and then filter down to just those with more than one occurrence of the name:
let keys = [CNContactIdentifierKey as CNKeyDescriptor, CNContactFormatter.descriptorForRequiredKeys(for: .fullName)]
let request = CNContactFetchRequest(keysToFetch: keys)
var contactsByName = [String: [CNContact]]()
try! self.store.enumerateContacts(with: request) { contact, stop in
guard let name = CNContactFormatter.string(from: contact, style: .fullName) else { return }
contactsByName[name] = (contactsByName[name] ?? []) + [contact] // or in Swift 4, `contactsByName[name, default: []].append(contact)`
}
let duplicates = contactsByName.filter { $1.count > 1 }
Related
Firestore query multiple documents with for loop
I am trying to query multiple documents using a for-loop. My database set up looks like this: users -> wishlists -> all the users Wishlists(containing different Wishlists with name) -> wünsche The items are getting retrieved but in the wrong order. I tried couple of different things but nothing worked so far. func getWishes() { let db = Firestore.firestore() let userID = Auth.auth().currentUser!.uid var counter = 0 for list in self.dataSourceArray { print(list.name) // -> right order } for list in self.dataSourceArray { db.collection("users").document(userID).collection("wishlists").document(list.name).collection("wünsche").getDocuments() { ( querySnapshot, error) in print(list.name) // -> wrong order if let error = error { print(error.localizedDescription) }else{ // create new Wish array var wList: [Wish] = [Wish]() for document in querySnapshot!.documents { let documentData = document.data() let wishName = documentData["name"] wList.append(Wish(withWishName: wishName as! String, checked: false)) } self.dataSourceArray[counter].wishData = wList counter += 1 } } } } I am calling this function inside another function that retrieves all the wishlist in the right order: func getWishlists() { let db = Firestore.firestore() let userID = Auth.auth().currentUser!.uid db.collection("users").document(userID).collection("wishlists").order(by: "listIDX").getDocuments() { ( querySnapshot, error) in if let error = error { print(error.localizedDescription) }else { // get all documents from "wishlists"-collection and save attributes for document in querySnapshot!.documents { let documentData = document.data() let listName = documentData["name"] let listImageIDX = documentData["imageIDX"] // if-case for Main Wishlist if listImageIDX as? Int == nil { self.dataSourceArray.append(Wishlist(name: listName as! String, image: UIImage(named: "iconRoundedImage")!, wishData: [Wish]())) // set the drop down menu's options self.dropDownButton.dropView.dropDownOptions.append(listName as! String) self.dropDownButton.dropView.dropDownListImages.append(UIImage(named: "iconRoundedImage")!) }else { self.dataSourceArray.append(Wishlist(name: listName as! String, image: self.images[listImageIDX as! Int], wishData: [Wish]())) self.dropDownButton.dropView.dropDownOptions.append(listName as! String) self.dropDownButton.dropView.dropDownListImages.append(self.images[listImageIDX as! Int]) } // reload collectionView and tableView self.theCollectionView.reloadData() self.dropDownButton.dropView.tableView.reloadData() } } self.theCollectionView.isHidden = false self.getWishes() } } *DataSourceArray in the right order: * Main Wishlist, Goals, boost Output from 2nd print-test: boost, Goals, Main Wishlist
Seems as though you are trying to make a bunch of API calls at once and it is returning values at different times. You could attempt to make your calls synchronously to maintain order or you could try to use dispatch groups like the pseudo code below: let myGroup = DispatchGroup() struct DataItem { let order: Int let data: DataYouWantToSave } var fetchedData = [DataItem]() for i in list { myGroup.enter() let dataItem = DataItem() dataItem.order = i db.collection... print("Finished request \(i)") dataItem.data = DataYouWantToSave fetchedData.apped(dataItem) myGroup.leave() } } myGroup.notify(queue: .main) { print("Finished all requests.") // Reorder your array of data items here. let sortedArray = fetchedData.sorted(by: { $0.order > $1.order }) // If you just want the array of data values let newData: [DataYouWantToSave] = sortedArray.map { $0.data } }
How to fetch contacts and store in array on iOS?
I am working on a Tinder Swiping application for iOS Contacts. I have imported the contacts library and successfully have gotten the contacts to print in the console. However, I am trying to dynamically add those names to a card like Tinder. I have created a model class to hold the name, however, I am unable to append my data to that model. struct ContactInfo { var name: String } let contactInfo = [ContactInfo]() func fetchContacts(){ let contactStore = CNContactStore() var contacts = [CNContact]() let keys = [CNContactFormatter.descriptorForRequiredKeys(for: .fullName)] let request = CNContactFetchRequest(keysToFetch: keys) do { try contactStore.enumerateContacts(with: request) { (contact, stop) in contacts.append(contact) var info = contact.givenName + " " + contact.familyName print(info) } } catch { print(error.localizedDescription) } } var userModels : [ContactInfo] = { var model : [ContactInfo] = [] for n in 1...10 { model.append(ContactInfo(name: names[Int(arc4random_uniform(UInt32(names.count)))])) } return model }() I would like all of my contacts to append to the model variable which is then returned to the cards.
As my understanding, the enumerateContacts is asynchronous. It means that, when your app is executing the line to create the userModels array, the contacts aren't fetched yet : so your array stays empty. I would try to move the userModels array creation in another controller, where you are displaying your cards. To achieve that, you can use a delegate, that receives the fetched contacts as a parameter. Then, you can assign your array with this parameter content and create your cards' content. Here is a great tutorial on how to use a delegate with Swift. Hope this will help you.
I have created helper class for that. That might help you. Example ContactSyncHelper.sharedInstance.getAllContacts { (contacts, error) in if error { print("error") }else { print(contacts) } } ContactSyncHelper import UIKit import Foundation import Contacts let kphone_number = "phone_number" let kcountry_code = "country_code" class ContactSyncHelper: NSObject { static let sharedInstance: ContactSyncHelper = { let instance = ContactSyncHelper() // setup code return instance }() // MARK: - Initialization Method override init() { super.init() } //Example // ContactSyncHelper.sharedInstance.getAllContacts { (contacts, error) in // if error { // print("error") // }else { // print(contacts) // } // } func getAllContacts(completion: ([NSMutableDictionary],Bool) -> ()) { switch CNContactStore.authorizationStatus(for: .contacts) { // Update our UI if the user has granted access to their Contacts case .authorized: break // Prompt the user for access to Contacts if there is no definitive answer case .notDetermined : completion([],true) break // Display a message if the user has denied or restricted access to Contacts case .denied, .restricted: //CommData.showAlert(self, withMsg: "Permission was not granted for Contacts.", withTitle: "Privacy Warning!", action: nil) completion([],true) break } let contactStore = CNContactStore() let keysToFetch = [ CNContactFormatter.descriptorForRequiredKeys(for: .fullName), CNContactEmailAddressesKey, CNContactPhoneNumbersKey, CNContactImageDataAvailableKey, CNContactThumbnailImageDataKey] as [Any] // Get all the containers var allContainers: [CNContainer] = [] do { allContainers = try contactStore.containers(matching: nil) } catch { print("Error fetching containers") } var arrayNumbers: [NSMutableDictionary] = [] for container in allContainers { let fetchPredicate = CNContact.predicateForContactsInContainer(withIdentifier: container.identifier) do { let containerResults = try contactStore.unifiedContacts(matching: fetchPredicate, keysToFetch: keysToFetch as! [CNKeyDescriptor]) containerResults.forEach { (contact:CNContact) in contact.phoneNumbers.forEach { (justPhone:CNLabeledValue) in let numberValue = justPhone.value let countryCode = numberValue.value(forKey: "countryCode") as? String var strphone = numberValue.stringValue strphone = strphone.replacingOccurrences(of: "(", with: "") strphone = strphone.replacingOccurrences(of: ")", with: "") strphone = strphone.replacingOccurrences(of: "-", with: "") strphone = strphone.replacingOccurrences(of: "+", with: "") strphone = strphone.components(separatedBy: .whitespaces).joined() if strphone.hasPrefix("0"){ strphone.remove(at: (strphone.startIndex)) } if(countryCode != nil) { var countryCode1:String = self.getCountryPhonceCode(country: countryCode!) if strphone.hasPrefix(countryCode1) { strphone = strphone.deletingPrefix(countryCode1) } countryCode1 = "+\(countryCode1)" let dict = NSMutableDictionary() dict.setValue(strphone, forKey: kphone_number) dict.setValue(countryCode1, forKey: kcountry_code) arrayNumbers.append(dict) } } } } catch { print("Error fetching results for container") } } completion(arrayNumbers,false) } func getCountryPhonceCode (country : String) -> String { if country.count == 2 { let x : [String] = ["972","IL","93","AF","355","AL","213","DZ","1","AS","376","AD","244","AO","1","AI","1","AG","54","AR","374","AM","297","AW","61","AU","43","AT","994","AZ","1","BS","973","BH","880","BD","1","BB","375","BY","32","BE","501","BZ","229","BJ","1","BM","975","BT","387","BA","267","BW","55","BR","246","IO","359","BG","226","BF","257","BI","855","KH","237","CM","1","CA","238","CV","345","KY","236","CF","235","TD","56","CL","86","CN","61","CX","57","CO","269","KM","242","CG","682","CK","506","CR","385","HR","53","CU" ,"537","CY","420","CZ","45","DK" ,"253","DJ","1","DM","1","DO","593","EC","20","EG" ,"503","SV","240","GQ","291","ER","372","EE","251","ET","298","FO","679","FJ","358","FI","33","FR","594","GF","689","PF","241","GA","220","GM","995","GE","49","DE","233","GH","350","GI","30","GR","299","GL","1","GD","590","GP","1","GU","502","GT","224","GN","245","GW","595","GY","509","HT","504","HN","36","HU","354","IS","91","IN","62","ID","964","IQ","353","IE","972","IL","39","IT","1","JM","81","JP","962","JO","77","KZ","254","KE","686","KI","965","KW","996","KG","371","LV","961","LB","266","LS","231","LR","423","LI","370","LT","352","LU","261","MG","265","MW","60","MY","960","MV","223","ML","356","MT","692","MH","596","MQ","222","MR","230","MU","262","YT","52","MX","377","MC","976","MN","382","ME","1","MS","212","MA","95","MM","264","NA","674","NR","977","NP","31","NL","599","AN","687","NC","64","NZ","505","NI","227","NE","234","NG","683","NU","672","NF","1","MP","47","NO","968","OM","92","PK","680","PW","507","PA","675","PG","595","PY","51","PE","63","PH","48","PL","351","PT","1","PR","974","QA","40","RO","250","RW","685","WS","378","SM","966","SA","221","SN","381","RS","248","SC","232","SL","65","SG","421","SK","386","SI","677","SB","27","ZA","500","GS","34","ES","94","LK","249","SD","597","SR","268","SZ","46","SE","41","CH","992","TJ","66","TH","228","TG","690","TK","676","TO","1","TT","216","TN","90","TR","993","TM","1","TC","688","TV","256","UG","380","UA","971","AE","44","GB","1","US","598","UY","998","UZ","678","VU","681","WF","967","YE","260","ZM","263","ZW","591","BO","673","BN","61","CC","243","CD","225","CI","500","FK","44","GG","379","VA","852","HK","98","IR","44","IM","44","JE","850","KP","82","KR","856","LA","218","LY","853","MO","389","MK","691","FM","373","MD","258","MZ","970","PS","872","PN","262","RE","7","RU","590","BL","290","SH","1","KN","1","LC","590","MF","508","PM","1","VC","239","ST","252","SO","47","SJ","963","SY","886","TW","255","TZ","670","TL","58","VE","84","VN","284","VG","340","VI","678","VU","681","WF","685","WS","967","YE","262","YT","27","ZA","260","ZM","263","ZW"] var keys = [String]() var values = [String]() let whitespace = NSCharacterSet.decimalDigits //let range = phrase.rangeOfCharacterFromSet(whitespace) for i in x { // range will be nil if no whitespace is found // if (i.rangeOfCharacterFromSet(whitespace) != nil) { if (i.rangeOfCharacter(from: whitespace, options: String.CompareOptions.caseInsensitive) != nil) { values.append(i) } else { keys.append(i) } } let countryCodeListDict:NSDictionary = NSDictionary(objects: values as [String], forKeys: keys as [String] as [NSCopying]) if let _: AnyObject = countryCodeListDict.value(forKey: country.uppercased()) as AnyObject { return countryCodeListDict[country.uppercased()] as! String } else { return "" } } else { return "" } } }
Retrieve data from "getDocument" query inside another "getDocument" query
I have a problem in my code retrieving data from Firestore. I have 2 classes in my code: Exercise and Tag. And I have 2 collections in my FirestoreDatabase: exercises and tags I need to fecth all the "exercise" documents from the "exercises" collection. Every "exercise" document has a field called "tags" that is an array of strings. Each string of the array contains the "id" that refers to the document that "tag" has on "tags" collection. So, querying this id in "tags" collection, enables me to get the correct "tag" document and access all its data. And this is exactly what I want to do in my code. I need to fecth all the exercises into a Exercise object and for that I have to use a getDocument query inside another getDocument query in order to get "Tags" of the exercise from "tags" collection This are my classes Tag and Exercise: class Tag { var id: String? var type: String? var description: String? init(id: String, type: String, description: String) { self.id = id self.type = type self.description = description } } class Exercise { let id: String? let group: String? let tags: [Tag] let title : String! init(id: String, group: String, tags: [Tag], title: String){ self.id = id self.group = group self.tags = tags self.title = title } } And this is the code where I fetch my "exercises" from Firestore database: func fetchExercises(completion: #escaping ([Exercise]) -> ()) { let exercisesRef = Firestore.firestore().collection("exercises") exercisesRef.getDocuments() { (querySnapshot, err) in var exercisesArray = [Exercise]() if let err = err { print("Error getting documents: \(err)") } else { for document in querySnapshot!.documents { //print("\(document.documentID) => \(document.data())") let myData = document.data() let exercise_ID = document.documentID let exercise_group = myData["Group"] as! String let tagsArray = myData["Tags"] as! [String] var exercise_tags: [Tag] = [Tag]() for tag in tagsArray { let tagID: String = tag fetchTagfromID(tagID: tagID) { (tag: Tag) in exercise_tags.append(tag) } } let exercise_title = myData["Title"] as! String exercisesArray.append(Exercise(id: exercise_ID, group: exercise_group, tags: exercise_tags, title: exercise_title, )) } DispatchQueue.main.async{ print("EXERCISE FETCH HAS FINIS") completion(exercisesArray) } } } } func fetchTagfromID(tagID: String, completion: #escaping (Tag) -> ()) { let tagRef = Firestore.firestore().collection("tags").document(tagID) tagRef.getDocument() { (document, err) in if let err = err { print("Error getting documents: \(err)") } else { let myData = document?.data() let tagDescription: String = myData!["description"] as! String let tagType: String = myData!["type"] as! String let tag: Tag = Tag(id: tagID, type: tagType, description: tagDescription) DispatchQueue.main.async{ print("TAGS FETCH HAS FINISHED") completion(tag) } } } } My problems come with the timing (queues) code is executed. I need to fill "exercise_tags" first (secondary getDocument query) and then continue and finish fetchExercise (main getDocument query) but Firestore do not allow (or do not know how) to do that. Code finishes first the main getDocument query (fetChExercises) and then get back to finish secondary getDocument Query (fetchTagfromID). In summary I need that in runtime I get this log: TAGS FETCH HAS FINISHED EXERCISES FETCH HAS FINISHED and now I am getting the opposite. Do you know guys how to solve this problem? Maybe changing dispatch queing... I now how to solve the problem doing this in two steps but elegant solution is doing everything in one step. This is, fetchExercises. Thank you!
So what you need to do is get all tags for one Exercise then fetch another Exercise. and after all fetch call completion handler to update UI. I made some change in code you can check it. var exercisesArray = [Exercise]() var listFromFetchExercise = []() //it will contain all object of array querySnapshot!.documents. set DataType according to that. var completion_exercises_Listner: () -> () func fetchExercises(completion: #escaping ([Exercise]) -> ()) { let exercisesRef = Firestore.firestore().collection("exercises") exercisesRef.getDocuments() { (querySnapshot, err) in if let err = err { print("Error getting documents: \(err)") } else { listFromFetchExercise = querySnapshot!.documents fetchAllExercise() DispatchQueue.main.async{ print("EXERCISE FETCH HAS FINIS") completion(exercisesArray) } } } } func fetchAllExercise(completion: #escaping ([Exercise]) -> ()){ fetchExercise() completion_exercises_Listner = { completion(exercisesArray); } } func fetchExercise(index:Int = 0) { let document = listFromFetchExercise[index] let myData = document.data() let exercise_ID = document.documentID let exercise_group = myData["Group"] as! String let tagsArray = myData["Tags"] as! [String] var exercise_tags: [Tag] = [Tag]() fetchTagsFromIDS(tagsArray) { (tag: [Tag]) in exercise_tags.append(contentsOf: tag) let exercise_title = myData["Title"] as! String exercisesArray.append(Exercise(id: exercise_ID, group: exercise_group, tags: exercise_tags, title: exercise_title, DispatchQueue.main.async{ exercisesArray.append(tag) if (index + 1) < exercisesArray.count { fetchExercise(index+1,) }else{ //Done completion_exercises_Listner() } } )) } var listofTags = [String]() var resultofTags = [Tag]() func fetchTagsFromIDS(tagIDS:[String],completion: #escaping (_ tags:[ String]) -> ()){ listofTags = tagIDS; fetchTagfromID() //Start With first tag completionListner = { completion(resultofTags); } } var completionListner: () -> () func fetchTagfromID(index:Int = 0) { let tagRef = Firestore.firestore().collection("tags").document(tagID) tagRef.getDocument() { (document, err) in if let err = err { print("Error getting documents: \(err)") } else { let myData = document?.data() let tagDescription: String = myData!["description"] as! String let tagType: String = myData!["type"] as! String let tag: Tag = Tag(id: tagID, type: tagType, description: tagDescription) DispatchQueue.main.async{ print("TAGS FETCH HAS FINISHED") resultofTags.append(tag) if (index + 1) < listofTags.count { fetchTagfromID(index+1,) }else{ //Done completionListner() } //completion(tag) } } } }
How to get custom value back from Spotlight with CSCustomAttributeKey
I am trying to get some data back from Core Spotlight which I am storing using a custom attribute key. Tested this on macOS and iOS as well, the result is always the same. My test class: import CoreSpotlight class SpotlightSearch { let domainId = "com.company.some" let originalDataKeyName: String init() { self.originalDataKeyName = domainId.replacingOccurrences(of: ".", with: "_") + "_originalData" } func addToIndex(title: String, content: String) { guard let originalDataKey = CSCustomAttributeKey(keyName: originalDataKeyName, searchable: false, searchableByDefault: false, unique: false, multiValued: false) else { return } let uniqueId = "MyUniqueId" + title let originalContent = NSString(string: content) let attributeSet = CSSearchableItemAttributeSet(itemContentType: kUTTypeText as String) attributeSet.title = title attributeSet.setValue(originalContent, forCustomKey: originalDataKey) let item = CSSearchableItem(uniqueIdentifier: uniqueId, domainIdentifier: domainId, attributeSet: attributeSet) CSSearchableIndex.default().indexSearchableItems([item]) { error in if let error = error { print("Indexing error: \(error.localizedDescription)") } else { print("Item '\(title)' successfully indexed!") } } } var query: CSSearchQuery? func search(title: String) { var allItems = [CSSearchableItem]() let queryString = "title == '\(title)'cd" let attributes = [ "title", originalDataKeyName ] let newQuery = CSSearchQuery(queryString: queryString, attributes: attributes) newQuery.foundItemsHandler = { (items: [CSSearchableItem]) -> Void in allItems.append(contentsOf: items) } newQuery.completionHandler = { [weak self] (error: Error?) -> Void in guard let originalDataKeyName = self?.originalDataKeyName, let originalDataKey = CSCustomAttributeKey(keyName: originalDataKeyName) else { return } print("Search complete") for item in allItems { let attributeSet = item.attributeSet let customData = attributeSet.value(forCustomKey: originalDataKey) // Always nil if customData == nil { print("\(String(describing: originalDataKeyName)) not found in \(attributeSet.description)") } else if let originalData = customData as? NSData { let data = Data(referencing: originalData) if let originalString = String(data: data, encoding: .utf8) { print("Found '\(originalString)'") } } } } query = newQuery newQuery.start() } } On app init: let newSpotlightSearch = SpotlightSearch() newSpotlightSearch.addToIndex(title: "Banana", content: "🍌") Later: spotlightSearch.search(title: "Banana") It will find the title, but will not give me back the custom attribute value. If I put a breakpoint after "// Always nil" and use po attributeSet I will get (lldb) po attributeSet { "_kMDItemBundleID" = "de.axelspringer.SearchMac"; "_kMDItemDomainIdentifier" = "com.company.some"; "_kMDItemExpirationDate" = "2018-08-26 00:00:00 +0000"; "_kMDItemExternalID" = MyUniqueIdBanana; "com_company_some_originalData" = "\Ud83c\Udf4c"; kMDItemTitle = Banana; } So the value is there, but Spotlight will not return it to me. Already tried to use NSData instead of NSString for the custom attribute, but same result. Also found this orphaned question in the Apple developer forums: CSCustomAttributeKey valueForCustomKey not working
I believe it's iOS issue. While it's not fixed, maybe Apple will allow you to use a private API to do your thing. So, attributeSet has private Dictionaries attributes and customAttributes. You can try to get those values using Key Value Coding and ObjC: NSDictionary *attributes = [attributeSet valueForKey:#"attributes"]; id customData = attributes[originalDataKeyName]; OR NSDictionary *customAttributes = [attributeSet valueForKey:#"customAttributes"]; id customData = customAttributes[originalDataKeyName]; Key type in those dictionaries is either NSString* or CSCustomAttributeKey*, so you can try supplying both originalDataKeyName and originalDataKey.
How to get a CNContact phone number(s) as string in Swift?
I am attempting to retrieve the names and phone number(s) of all contacts and put them into arrays with Swift in iOS. I have made it this far: func findContacts() -> [CNContact] { marrContactsNumber.removeAllObjects() marrContactsName.removeAllObjects() let store = CNContactStore() let keysToFetch = [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactPhoneNumbersKey] let fetchRequest = CNContactFetchRequest(keysToFetch: keysToFetch) var contacts = [CNContact]() do { try store.enumerateContactsWithFetchRequest(fetchRequest, usingBlock: { (let contact, let stop) -> Void in contacts.append(contact) self.marrContactsName.addObject(contact.givenName + " " + contact.familyName) self.marrContactsNumber.addObject(contact.phoneNumbers) print(contact.phoneNumbers) } catch let error as NSError { print(error.localizedDescription) } print(marrContactsName.count) print(marrContactsNumber.count) return contacts } Once completed, marrContactsName contains an array of all my contacts' names exactly as expected. i.e. "John Doe". However, marrContactsNumber returns an array of values like [<CNLabeledValue: 0x158a19950: identifier=F831DC7E-5896-420F-AE46-489F6C14DA6E, label=_$!<Work>!$_, value=<CNPhoneNumber: 0x158a19640: countryCode=us, digits=6751420000>>, <CNLabeledValue: 0x158a19a80: identifier=ECD66568-C6DD-441D-9448-BDEDDE9A68E1, label=_$!<Work>!$_, value=<CNPhoneNumber: 0x158a199b0: countryCode=us, digits=5342766455>>] I would like to know how to retrieve JUST the phone number(s) as a string value(s) i.e. "XXXXXXXXXX". Basically, how to call for the digit(s) value. Thanks!
I found the solution: (contact.phoneNumbers[0].value as! CNPhoneNumber).valueForKey("digits") as! String
you can get contact.phoneNumbers from CNLabeledValue: for phoneNumber in contact.phoneNumbers { if let number = phoneNumber.value as? CNPhoneNumber, let label = phoneNumber.label { let localizedLabel = CNLabeledValue.localizedStringForLabel(label) print("\(localizedLabel) \(number.stringValue)") } }
/* Get only first mobile number */ let MobNumVar = (contact.phoneNumbers[0].value as! CNPhoneNumber).valueForKey("digits") as! String print(MobNumVar) /* Get all mobile number */ for ContctNumVar: CNLabeledValue in contact.phoneNumbers { let MobNumVar = (ContctNumVar.value as! CNPhoneNumber).valueForKey("digits") as? String print(MobNumVar!) } /* Get mobile number with mobile country code */ for ContctNumVar: CNLabeledValue in contact.phoneNumbers { let FulMobNumVar = ContctNumVar.value as! CNPhoneNumber let MccNamVar = FulMobNumVar.valueForKey("countryCode") as? String let MobNumVar = FulMobNumVar.valueForKey("digits") as? String print(MccNamVar!) print(MobNumVar!) }
Here is how you do it in swift 4 func contactPicker(_ picker: CNContactPickerViewController, didSelect contactProperty: CNContactProperty) { if let phoneNo = contactProperty.value as? CNPhoneNumber{ txtPhone.text = phoneNo.stringValue }else{ txtPhone.text="" } }
Here's a Swift 5 solution. import Contacts func sendMessageTo(_ contact: CNContact) { let validTypes = [ CNLabelPhoneNumberiPhone, CNLabelPhoneNumberMobile, CNLabelPhoneNumberMain ] let numbers = contact.phoneNumbers.compactMap { phoneNumber -> String? in guard let label = phoneNumber.label, validTypes.contains(label) else { return nil } return phoneNumber.value.stringValue } guard !numbers.isEmpty else { return } // process/use your numbers for this contact here DispatchQueue.main.async { self.sendSMSText(numbers) } } You can find available values for the validTypes array in the CNPhoneNumber header file. They are: CNLabelPhoneNumberiPhone CNLabelPhoneNumberMobile CNLabelPhoneNumberMain CNLabelPhoneNumberHomeFax CNLabelPhoneNumberWorkFax CNLabelPhoneNumberOtherFax CNLabelPhoneNumberPager
The definition of a CNLabeledValue: The CNLabeledValue class is a thread-safe class that defines an immutable value object that combines a contact property value with a label. For example, a contact phone number could have a label of Home, Work, iPhone, etc. CNContact.phoneNumbers is an array of CNLabeledValues and each CNLabeledValue has a label and a value. To print the phoneNumbers corresponding to a CNContact you can try: for phoneNumber in contact.phoneNumbers { print("The \(phoneNumber.label) number of \(contact.givenName) is: \(phoneNumber.value)") }
In swift 3 you can get direclty if item.isKeyAvailable(CNContactPhoneNumbersKey){ let phoneNOs=item.phoneNumbers let phNo:String for item in phoneNOs{ print("Phone Nos \(item.value.stringValue)") }
Keeping things simple: let phoneNumbers: [String] = contact.phoneNumbers.compactMap { (phoneNumber: CNLabeledValue) in guard let number = phoneNumber.value.value(forKey: "digits") as? String else { return nil } return number }
for Swift 5+ func removeSpecialCharactersFromContactNumberOfUser(_ contactNo : String) -> String? { let digits = CharacterSet(charactersIn: "0123456789").inverted let modifiedContactNo = contactNo.components(separatedBy: digits).joined(separator: "") if modifiedContactNo.count > 9 { return modifiedContactNo } else { return nil } } var number = phone.value.stringValue number = number.starts(with: "+91") ? number.replacingOccurrences(of: "+91", with: "") : number if let formattedNumber = removeSpecialCharactersFromContactNumberOfUser(number) { //use this formattedNumber } This is to remove +91 from your phone number and it's working fine.
Swift 3 "_$!<Mobile>!$_" This item is written to create difference as well as putting a piece of opportunity to rely on various options. for con in contacts { for num in con.phoneNumbers { if num.label == "_$!<Mobile>!$_" //Please Don't Change this! { self.contactNames.append(con.givenName) self.contactNums.append(num.value.stringValue) break } else { continue } } } Here we have num.value.stringValue
fetch without country code from phone contacts and also removed unwanted text such as dash, spaces etc.. and also post from phonetextfield import ContactsUI var phoneString:String! func contactPicker(_ picker: CNContactPickerViewController, didSelect contact: CNContact) { let numbers = contact.phoneNumbers.first let a = (numbers?.value)?.stringValue ?? "" let myString = a let formattedString = myString.replacingOccurrences(of: " ", with: "") let newFormattedString = formattedString.replacingOccurrences(of: "(", with: "") let formatstring = newFormattedString.replacingOccurrences(of: ")", with: "") let last10 = formatstring.replacingOccurrences(of: "-", with: "") phoneString = String(last10.suffix(10)) phonetextField.text = phoneString } func contactPickerDidCancel(_ picker: CNContactPickerViewController) { self.dismiss(animated: true, completion: nil) } #IBAction func inviteButton(_ sender : Any) { if phoneString == nil{ phoneString = phonetextField.text! //fetching from phonetextfield Phone = phoneString } else { Phone = phoneString //fetching from phone contacts } }