iOS 9: get CNContact country code and phone number - ios

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.

Related

The app is working on my phone but not any other device

I tried to solve this problem for a couple of days before submitting the app, so here it goes. I am developing an app that uses UserDefaults to save a string from a different view controller with a press of a button and to transfer it a table view in the next view controller. However, for some unknown reason, the app works perfectly on iPhone X (and iPhone XR), but it does not save the string and react to a press of a button whenever I run the app on a different iPhone model. Here is my code:
//FirstViewController
#IBAction func buttonIsPressed(_ sender: Any) {
if var items = UserDefaults.standard.object(forKey: "items") as? [String]{
var newitems = textField.text!.components(separatedBy: CharacterSet(charactersIn: ", []()\n.:"))
print(items)
if newitems.contains(""){
newitems.removeAll { $0 == ""}
items.append(contentsOf: newitems)
UserDefaults.standard.set(items, forKey: "items")
}else{
let newitems = textField.text!.components(separatedBy: CharacterSet(charactersIn: ", []()\n.:"))
UserDefaults.standard.set(newitems, forKey: "items")
}
textField.text = ""
}
}
//SecondViewController
var scannedText: String = "Detected text can be edited here." {
didSet {
textView.text = scannedText
let str = scannedText.uppercased()
let allergens = UserDefaults.standard.array(forKey: "items") as! [String]
let string = str.components(separatedBy: CharacterSet(charactersIn: ", []()\n.:"))
print(string)
for allergen in allergens{
if string.contains(String(Substring(allergen))) == true {
print("I found the string \(allergen)")
allegenLabel.text = "Not safe"
allegenLabel.alpha = 1 //Make the label visible
allegenLabel.textColor = .red
// let attributedString = allergen.highlight([allergen], this: .red)
// textView.attributedText = attributedString
allergensFound.append(allergen)
print(allergensFound)
}
if string.contains(String(Substring(allergen))) == false {
allegenLabel.text = "Safe"
allegenLabel.alpha = 1 //Make the label visible
allegenLabel.textColor = UIColor.colorGreen
// table.reloadData()
}
}
}
}
Please ask me if you need further description in the code. Thanks!
Edit: To be more exact I am creating a code that will find a string in the array of strings that will then notify the user with AllegenLabel.text. The whole app is divided into different view controllers. The user inputs in a string using a text field in FirstViewController and then that string has to be found in a different array of strings in SecondViewController. For some reason UserDefaults successfully stores and retrieves the string on my phone but does not work on different devices. For example, whenever I click the save button in FirstViewController, a string has to be stored and shown in the console, but for some unknown reason it does not work. Sorry if I was unclear
I think you can try:
UserDefaults.standard.synchronize()
maybe...

retrieve array elements from firebase in swift

I am completely new to swift and firebase, and I am having difficulties in retrieving array-elements from firebase database
So this is my firebase database
I can retrieve the other elements like this:
database reference
class ViewController: UIViewController {
var ref: FIRDatabaseReference?
let fileName : String = "jsonFile"
method
func parseFirebaseResponse() {
ref?.child("Vandreture").child(fileName).observe(.value, with:
{ (snapshot) in
let dict = snapshot.value as? [String: AnyObject]
let navn = dict!["navn"] as? String
print(navn as Any)
let type = dict!["type"] as? String
print(type as Any)
let Længde = dict!["length"] as? String
print(Længde as Any)
let link = dict!["link"] as? String
print(link as Any)
})
}
the console shows the result
But I have searched for a way to retrieve longitude/latitude pairs,
The first pair should be
latitude 109.987
longitude 102.987
- but so far without luck - help would really be appreciated :)
I think you should restructure your database to something like this:
Vandreture {
jsonFile {
length: "16.2"
link: "www.second.com"
navn: "Kagerup rundt"
positions {
-KqXukxnw3mL38oPeI4y {
x: "102.987"
y: "109.987"
}
-KqXukxnw3mL38oPeI5- {
x: "108.234"
y: "99.098"
}
}
}
}
Then getting your coordinates would be far easier!
Get your "position" as a dictionary (aka [String: AnyObject]).
Then get array of latitude and longitude. Then map of each element from latitude and longitude into a tuple or something like that.
guard let position = dict!["position"] as? [String: AnyObject],
let latitudeArray = position["latitude"] as? [String],
let longitudeArray = position["longitude"] as? [String] else { return }
print(latitudeArray)
print(longitudeArray)
thanks Victor Apeland - now I changed the structure of the database. I was not able to do it exactly as you suggested!!
I concatenated the lon/lat as a string (not the best - i know - I'm a newbie to swift, and firebase is not well documented)
to retrieve the individual elements from my long/lat, I used this method, and I was particular interested in the first reading, so I made a list, populated it, and got the first element, parsing it to a new method
func responsePosition() {
var positions = [String]()
ref?.child("Vandreture").child(fileName).child("position").observe(.value, with: { (snapshot) in
for snap in snapshot.children {
let userSnap = snap as! FIRDataSnapshot
let uid = userSnap.key //the uid of each user
let userDict = userSnap.value as! String
positions.append(userDict)
}
let pos : String = positions[0]
self.formatPositions(pos : pos)
})
}
In my firebase lon-lat was divided by a space, so I split the string and cast to double.
func formatPositions(pos : String) {
let lotLangDivided = pos.components(separatedBy: " ")
let longitude = Double(lotLangDivided[0])
let latitude = Double(lotLangDivided[1])
}
now I got the result i wanted
- thanks for the help Victor and Khuong :)

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

Swift nested filter optimization?

I was trying to do something in Swift that would be easy in Objective-C using KVC. The new Contacts framework added in iOS9 is for the most part easier to use than the old AddressBook API. But finding a contact by its mobile phone number seems to be difficult. The predicates provided for finding contacts are limited to the name and the unique identifier. In Objective-C you could get all the contacts and then use an NSPredicate to filter on a KVC query. The structure is:
CNContact->phoneNumbers->(String, CNPhoneNumber->stringValue)
Presume in the code below that I fetched the contacts via:
let keys = [CNContactEmailAddressesKey,CNContactPhoneNumbersKey, CNContactFormatter.descriptorForRequiredKeysForStyle(.FullName)]
let fetchRequest = CNContactFetchRequest(keysToFetch: keys)
var contacts:[CNContact] = []
try! CNContactStore().enumerateContactsWithFetchRequest(fetchRequest) { ...
I want to compare the stringValue to a known value. Here's what I have so far from a playground:
import UIKit
import Contacts
let JennysPhone = "111-867-5309"
let SomeOtherPhone = "111-111-2222"
let AndAThirdPhone = "111-222-5309"
let contact1 = CNMutableContact()
contact1.givenName = "Jenny"
let phone1 = CNPhoneNumber(stringValue: JennysPhone)
let phoneLabeled1 = CNLabeledValue(label: CNLabelPhoneNumberMobile, value: phone1)
contact1.phoneNumbers.append(phoneLabeled1)
let contact2 = CNMutableContact()
contact2.givenName = "Billy"
let phone2 = CNPhoneNumber(stringValue: SomeOtherPhone)
let phoneLabeled2 = CNLabeledValue(label: CNLabelPhoneNumberMobile, value: phone2)
contact2.phoneNumbers.append(phoneLabeled2)
let contact3 = CNMutableContact()
contact3.givenName = "Jimmy"
let phone3 = CNPhoneNumber(stringValue: SomeOtherPhone)
let phoneLabeled3 = CNLabeledValue(label: CNLabelPhoneNumberMobile, value: phone3)
contact3.phoneNumbers.append(phoneLabeled3)
let contacts = [contact1, contact2, contact3]
let matches = contacts.filter { (contact) -> Bool in
let phoneMatches = contact.phoneNumbers.filter({ (labeledValue) -> Bool in
if let v = labeledValue.value as? CNPhoneNumber
{
return v.stringValue == JennysPhone
}
return false
})
return phoneMatches.count > 0
}
if let jennysNum = matches.first?.givenName
{
print("I think I found Jenny: \(jennysNum)")
}
else
{
print("I could not find Jenny")
}
This does work, but it's not efficient. On a device I would need to run this in a background thread, and it could take a while if the person has a lot of contacts. Is there a better way to find a contact by phone number (or email address, same idea) using the new iOS Contacts framework?
If you are looking for a more Swift-y way to do it:
let matches = contacts.filter {
return $0.phoneNumbers
.flatMap { $0.value as? CNPhoneNumber }
.contains { $0.stringValue == JennysPhone }
}
.flatMap casts each member of phoneNumbers from type CNLabeledValue to type CNPhoneNumber, ignoring those that cannot be casted.
.contains checks if any of these phone numbers matches Jenny's number.
I'm guessing you're wanting a more swift-y way, but obviously anything you can do in Obj-C can also be done in swift. So, you can still use NSPredicate:
let predicate = NSPredicate(format: "ANY phoneNumbers.value.digits CONTAINS %#", "1118675309")
let contactNSArray = contacts as NSArray
let contactsWithJennysPhoneNumber = contactNSArray.filteredArrayUsingPredicate(predicate)

CLPlacemark to string in iOS 9

I want to format CLPlacemark to string.
The well known way is to use ABCreateStringWithAddressDictionary but it was deprecated in iOS 9. Warning tells me to use CNPostalAddressFormatter instead.
However, CNPostalAddressFormatter can only format CNPostalAddress. There is no way to properly convert CLPlacemark to CNPostalAddress; only these 3 properties are shared by CLPlacemark and CNPostalAddress: country, ISOcountryCode, and postalCode.
So how should I format CLPlacemark to string now?
Take the placemark's addressDictionary and use its "FormattedAddressLines" key to extract the address string. Note that this is an array of the lines of the string.
(You are correct, however, that the Apple developers tasked with converting to the Contacts framework seem to have forgotten completely about the interchange between Address Book and CLPlacemark. This is a serious bug in the Contacts framework - one of many.)
EDIT Since I posted that answer originally, Apple fixed this bug. A CLPlacemark now has a postalAddress property which is a CNPostalAddress, and you can then use a CNPostalAddressFormatter to get a nice multi-line address string. Be sure to import Contacts!
Swift 3.0
if let lines = myCLPlacemark.addressDictionary?["FormattedAddressLines"] as? [String] {
let placeString = lines.joined(separator: ", ")
// Do your thing
}
Swift 4.1 (and 3 & 4, save 1 line)
I read the question to ask 'How might I implement this?':
extension String {
init?(placemark: CLPlacemark?) {
// Yadda, yadda, yadda
}
}
Two Methods
I first went for porting the AddressDictionary method, as did other posters. But that means losing the power and flexibility of the CNPostalAddress class and formatter. Hence, method 2.
extension String {
// original method (edited)
init?(depreciated placemark1: CLPlacemark?) {
// UPDATE: **addressDictionary depreciated in iOS 11**
guard
let myAddressDictionary = placemark1?.addressDictionary,
let myAddressLines = myAddressDictionary["FormattedAddressLines"] as? [String]
else { return nil }
self.init(myAddressLines.joined(separator: " "))
}
// my preferred method - let CNPostalAddressFormatter do the heavy lifting
init?(betterMethod placemark2: CLPlacemark?) {
// where the magic is:
guard let postalAddress = CNMutablePostalAddress(placemark: placemark2) else { return nil }
self.init(CNPostalAddressFormatter().string(from: postalAddress))
}
}
Wait, what is that CLPlacemark → CNPostalAddress initializer??
extension CNMutablePostalAddress {
convenience init(placemark: CLPlacemark) {
self.init()
street = [placemark.subThoroughfare, placemark.thoroughfare]
.compactMap { $0 } // remove nils, so that...
.joined(separator: " ") // ...only if both != nil, add a space.
/*
// Equivalent street assignment, w/o flatMap + joined:
if let subThoroughfare = placemark.subThoroughfare,
let thoroughfare = placemark.thoroughfare {
street = "\(subThoroughfare) \(thoroughfare)"
} else {
street = (placemark.subThoroughfare ?? "") + (placemark.thoroughfare ?? "")
}
*/
city = placemark.locality ?? ""
state = placemark.administrativeArea ?? ""
postalCode = placemark.postalCode ?? ""
country = placemark.country ?? ""
isoCountryCode = placemark.isoCountryCode ?? ""
if #available(iOS 10.3, *) {
subLocality = placemark.subLocality ?? ""
subAdministrativeArea = placemark.subAdministrativeArea ?? ""
}
}
}
Usage
func quickAndDirtyDemo() {
let location = CLLocation(latitude: 38.8977, longitude: -77.0365)
CLGeocoder().reverseGeocodeLocation(location) { (placemarks, _) in
if let address = String(depreciated: placemarks?.first) {
print("\nAddress Dictionary method:\n\(address)") }
if let address = String(betterMethod: placemarks?.first) {
print("\nEnumerated init method:\n\(address)") }
}
}
/* Output:
Address Dictionary method:
The White House 1600 Pennsylvania Ave NW Washington, DC 20500 United States
Enumerated init method:
1600 Pennsylvania Ave NW
Washington DC 20500
United States
*/
Whoever read until here gets a free T-shirt. (not really)
*This code works in Swift 3 & 4, except that flatMap for removing nil values has been depreciated/renamed to compactMap in Swift 4.1 (Doc here, or see SE-187 for the rationale).
Swift 3.0 Helper Method
class func addressFromPlacemark(_ placemark:CLPlacemark)->String{
var address = ""
if let name = placemark.addressDictionary?["Name"] as? String {
address = constructAddressString(address, newString: name)
}
if let city = placemark.addressDictionary?["City"] as? String {
address = constructAddressString(address, newString: city)
}
if let state = placemark.addressDictionary?["State"] as? String {
address = constructAddressString(address, newString: state)
}
if let country = placemark.country{
address = constructAddressString(address, newString: country)
}
return address
}

Resources