How to get a CNContact phone number(s) as string in Swift? - ios
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
}
}
Related
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 "" } } }
is there a way to get the updated contacts faster?
I'm searching for a way to get added/deleted/modified contacts to send them to the server .. I used Realm since it's faster than coreData to save the contacts and on each refresh or when user re-enter the app .. I'm comparing the Realm Database (backup) with the sim contacts to detect if there is a change (insertion - modification - deletion).. the code is working fine but it isn't fast enough .. I tried using the ABAddressBookRegisterExternalChangeCallback in an objective C file but it wasn't handy since it's being called only when a user is changed while the app is in background and it doesn't give me anything useful ... also I tried CnContactStoredidchange notification but it's useless . here's the databaseRealmModel im using the fullname+phonenumber as a primaryKey to quickly fetch it from the database #objcMembers class RealmModel:Object { dynamic var fullName: String = "" dynamic var phoneNumber: String = "" dynamic var firstName: String = "" dynamic var lastName: String = "" dynamic var middleName: String = "" dynamic var identifier: String = "" dynamic var primaryKey :String = "" #objc dynamic var contactImage: Data? = nil convenience init(fullName: String, phoneNumber: String,firstName:String,lastName:String,middleName:String,contactImage:Data?,identifier:String) { self.init() self.fullName = fullName self.phoneNumber = phoneNumber self.firstName = firstName self.lastName = lastName self.middleName = middleName self.contactImage = contactImage self.identifier = identifier self.primaryKey = identifier + phoneNumber } override class func primaryKey() -> String? { return "primaryKey" } } 2) here's where i'm calling the method DispatchQueue.global(qos: .userInteractive).async { let start = CFAbsoluteTimeGetCurrent() Utilities.getCotacts { (added,deleted,modified) in let diff = CFAbsoluteTimeGetCurrent() - start print("Realm Took \(diff) seconds") print( "Realm Added Contacts" + String(added.count)) print("Realm deleted Contacts" + String(deleted.count)) print("Realm modified Contacts" + String(modified.count)) } 3) here's the implementation of the function class func requestForAccess(completionHandler: #escaping (_ accessGranted: Bool) -> Void) { let authorizationStatus = CNContactStore.authorizationStatus(for: CNEntityType.contacts) switch authorizationStatus { case .authorized: completionHandler(true) case .denied, .notDetermined: self.contactStore.requestAccess(for: CNEntityType.contacts, completionHandler: { (access, accessError) -> Void in if access { completionHandler(access) } else { if authorizationStatus == CNAuthorizationStatus.denied { completionHandler(false) } } }) default: completionHandler(false) } } class func getCotacts(completionHandler: #escaping (_ addedcontacts: ([RealmModel]),_ deletedContacts: ([RealmModel]),_ modifiedContacts: [RealmModel]) -> Void) { var addedContacts = [RealmModel]() var deletedContacts = [RealmModel]() var modifiedContacts = [RealmModel]() self.requestForAccess { (approved) in if approved == true { //getting All contacts in database let realm = try! Realm() let Arr = realm.objects(RealmModel.self) var initialArr = Array.init(Arr) let keysToFetch = [ CNContactFormatter.descriptorForRequiredKeys(for: .fullName), CNContactEmailAddressesKey, CNContactImageDataKey, CNContactPhoneNumbersKey,CNContactImageDataAvailableKey] as! [CNKeyDescriptor] let contactFetchRequest = CNContactFetchRequest(keysToFetch: keysToFetch) do { try! realm.write { try self.contactStore.enumerateContacts(with: contactFetchRequest) { contact, stop in //enumerating through contact list for number in contact.phoneNumbers { let firstName = contact.givenName let lastName = contact.familyName let middleName = contact.middleName let fullName = String(format: "%#%#%#%#%#", arguments: [Utilities.isStringNull(string: contact.givenName) ? "" : contact.givenName, Utilities.isStringNull(string: contact.middleName) ? "" : " ", Utilities.isStringNull(string: contact.middleName) ? "" : contact.middleName, Utilities.isStringNull(string: contact.familyName) ? "" : " ", Utilities.isStringNull(string: contact.familyName) ? "" : contact.familyName]) let phoneNumber = number.value.value(forKey: "digits") as? String var contactsModel = RealmModel(fullName: fullName, phoneNumber: phoneNumber!, firstName: firstName, lastName: lastName, middleName: middleName,contactImage:contact.imageData,identifier:contact.identifier) let databaseContact = realm.object(ofType: RealmModel.self, forPrimaryKey:contactsModel.primaryKey) // contact doesnt exist in database so its a new contact if(databaseContact == nil) { addedContacts.append(contactsModel) realm.add(contactsModel,update: false) //it's new contact no need to set update true } else { // User exists in Database if(databaseContact?.fullName != contactsModel.fullName) { // if full name has been changed let indexesOfModifiedNumber = initialArr.indices.filter({ initialArr[$0].primaryKey == contactsModel.primaryKey }) if indexesOfModifiedNumber.count > 0 { modifiedContacts.append(initialArr[indexesOfModifiedNumber.first!]) initialArr.remove(at:indexesOfModifiedNumber.first!) databaseContact?.fullName = contactsModel.fullName } } else { // No Change and contact is found let indexesOfModifiedNumber = initialArr.indices.filter({ initialArr[$0].primaryKey == contactsModel.primaryKey }) if (indexesOfModifiedNumber.count > 0) { initialArr.remove(at: indexesOfModifiedNumber.first!) } } } } } if initialArr.count > 0 { // deleted Contacts deletedContacts = initialArr for element in deletedContacts { realm.delete(element) } } completionHandler(addedContacts,deletedContacts,modifiedContacts) } } } else { print("access not approved") } } } since Realm is quicker at insertion and fetching I didn't find that big difference between it and coreData I'm testing them with a phone with 6000 contacts .. Core data needs 90 seconds to finish while realm needs 70 seconds
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.
Find duplicate contacts in Contacts Framework
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 }
iOS 9: get CNContact country code and phone number
I want to get the country code and phone number from CNContact on iOS 9. I tried many things but couldn't find a way. The best result I achieved is printing: <CNPhoneNumber: 0x7f886389a140: countryCode=us, digits=5555648583> Here's how I do that: func contactPicker(picker: CNContactPickerViewController, didSelectContact contact: CNContact) { print(contact.phoneNumbers.count) for number: CNLabeledValue in contact.phoneNumbers { print(number.value) } } What I want is the values for countryCode and digits. Any ideas how to access them in Swift? Thanks!
Unfortunately you can't get them since they are private. let numberValue = number.value let countryCode = numberValue.valueForKey("countryCode") as? String let digits = numberValue.valueForKey("digits") as? String This works but if you do something in this lines your app will most likely be rejected. You can see all the nice stuff you could use here. If you don't plan on uploading your app to the store the solution above is OK, otherwise I'd stick with some kind of regex knowing it can break in the future: countryCode=(\w{2}),.*digits=(.+)>$
Objective-C: [number.value valueForKey:#"countryCode"] Swift: number.value.valueForKey("countryCode") as? String valueForKey is not private, and your app will not get rejected.
They two members can be accessed via valueForKey: let countryCode = number.valueForKey("countryCode") as? String let digits = number.valueForKey("digits") as? String Please note that due to the fact that these two fields are part of a private API, there's no guarantee that in the future versions of the Contacts framework they won't be removed/replaced.
/* 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!) }
fetch contacts without country code, spaces ,braces and dash from contacts book with contact picker and textfield both *********** without country code 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 } }
To get Country code you can use this: (contact.phoneNumbers[0].value ).value(forKey: "countryCode") as! String in for loop, and key; "digits" is to get full phone number.