How to fetch contacts and store in array on iOS? - 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 ""
}
}
}

Related

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.

How to know if a Firebase has fetched a value

I have three functions getNewOrder(),storeOrderDetails(_ details:[String:String]) and getUserInfo(_ userID:String).
Function getNewOrder() is called first. It fetches new orders ( .childAdded values) and sends the dictionary to storeOrderDetails(_ details:[String:String]).
storeOrderDetails(_ details:[String:String])then segregate all the values and callsgetUserInfo(_ userID:String)` by passing it userID which was present in its details.
getUserInfo(_ userID:String) then fetches users details and returns user's
information.
However, the problem is [ userInfo = getUserInfo(_ userID:String) in storeOrderDetails(_ details:[String:String]) ] userInfo is always empty. Apparently func getUserInfo(_ userID:String) goes into a completion block after it has returned empty value.
I want these three functions to execute in sequential way.
Any advice is highly appreciated.
Please follow the below Links to review my code.
https://imgur.com/hNjvyDk
https://imgur.com/J0LMXMg
func childAdded(){
let ref = Database.database().reference().child("Orders").child(todaysDate)
ref.observe(.childAdded) { (snapshot) in
var details = [String:String]()
if let orderID = snapshot.key as? String {
ref.child(orderID).observeSingleEvent(of: .value, with: { (snap) in
self.newOrderTextView.text = ""
self.customerNameLabel.text = ""
self.customerPhoneLabel.text = ""
self.orderNumberLabel.text = ""
let enumerator = snap.children
while let rest = enumerator.nextObject() as? DataSnapshot {
details[rest.key as? String ?? ""] = rest.value as? String ?? ""
}
self.storeUserDetails(details)
})
}
}
}
func storeUserDetails(_ details:[String:String]){
if details["CustomerID"] != nil {
userInfo = getUserDetails(details["CustomerID"]!)
print(userInfo)
}
if !userInfo.isEmpty{
let order = OrderDatabase()
order.customerEmail = userInfo["Email"]!
order.customerName = userInfo["Name"]!
order.orderAcceptStatus = details["OrderStatus"]!
order.customerOrderNumber = details["orderNumber"]!
order.orderID = details["orderID"]!
order.time = details["Time"]!
order.customerFirebaseID = details["CustomerID"]!
self.orderDatabase[details["orderNumber"]!] = order
self.orderTable.reloadData()
}
}
func getUserDetails(_ userID:String) -> [String:String]{
var details = [String:String]()
let userDetailsReference = Database.database().reference().child("Users")
userDetailsReference.child(userID).observeSingleEvent(of: DataEventType.value, with: { (snapshot) in
if let dictionary = snapshot.value as? NSDictionary {
self.customerNameLabel.text = dictionary.value(forKey: "Name") as? String
self.customerPhoneLabel.text = dictionary.value(forKey: "Email") as? String
details["Name"] = dictionary.value(forKey: "Name") as? String
details["Email"] = dictionary.value(forKey: "Email") as? String
}
})
return details
}
From what I can see here, I am betting that the issue you are facing has to do with the fact that the methods are asynchronous. So one thing is not completely finished and some other method gets fired too soon. There are a few ways to deal with this issue. One is completion handlers, and the other is adding observers. Below is an example of doing both for Firebase. Here I'm asking a getLocationPhotos method to get all the photos from Firebase. Notice the observers and completion handler
func getLocationPhotos(coordinate:CLLocationCoordinate2D){
dbHandler.getImageFileNames(coordinateIn: coordinate) { (filenames) in
if filenames.isEmpty {
log.debug(String.warningGet + "filenames is empty")
return
}//if filenames.isEmpty
self.imageFiles = filenames.filter { $0 != "none" }
if self.imageFiles.isEmpty {
log.error(String.errorGet + "imageFiles array is empty")
return
}//if imageFiles.isEmpty
for file in self.imageFiles {
let reference = self.storageHandler.imageReference.child(file)
let download = self.imageView.sd_setImage(with: reference)
if let i = self.imageView.image {
self.imageArray.append(i)
self.collectionView.reloadData()
}//let i
download?.observe(.progress, handler: { (snapshot) in
guard let p = snapshot.progress else {
return
}//let p
self.progressView.progress = Float(p.fractionCompleted)
if self.progressView.progress == Float(1) {
self.progressView.isHidden = true
}
})//progress
download?.observe(.success, handler: { (snapshot) in
self.progressView.progress = 1
self.progressView.isHidden = true
self.collectionView.setNeedsLayout()
})//success
download?.observe(.failure, handler: { (snapshot) in
log.error(String.errorGet + "Error occured getting data from snapshot")
})//failure
}//for file
}//dbHandler

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 }

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

Resources