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
}
Related
I'm using a CLGeoCoder instance to get the city and country of a given set of coordinates (latitude and longitude) through its method reverseGeocodeLocation().
The documentation specifies that this data should be in CLPlacemark's country and locality properties but the results only seem to be inside addressDictionary property which was deprecated since iOS 11.
Image of debugging instance showing addressDictionary present but neither country nor locality
This therefore works but shows this warning on all three uses of addressDictionary:
'addressDictionary' was deprecated in iOS 11.0: Use #properties
let geocoder = CLGeocoder()
geocoder.reverseGeocodeLocation(location, preferredLocale: nil) { (placemarksArray, error) in
if let error = error {
print(error)
} else {
if let placemark = placemarksArray?.last {
var city = ""
if let ct = placemark.addressDictionary?["City"] {
city = "\(ct), "
}
var state = ""
if let st = placemark.addressDictionary?["State"] {
state = "\(st), "
}
var country = ""
if let cot = placemark.addressDictionary?["Country"] {
country = "\(cot)"
}
self.cityCountry = "\(city)\(state)\(country)"
}
}
}
I tried referencing the properties but they just return nil values.
Am I calling the wrong method or is this a bug?
Suddenly references to the country, locality and subAdministrativeArea started working! Couldn't figure out what caused them not to work earlier though.
I want to fetch full address by latitude and longitude in here map iOS premium sdk.In Android, I see there is the option to fetch address by latitude and longitude with ReverseGeocodeRequest but I did not find anything for iOS.
Currently, I am fetching the address from CLLocationCoordinate2D but I think it will be better if I will fetch it by HERE MAP sdk because I am using HERE MAP not Apple MAP. I have attached the android code below.
GeoCoordinate vancouver = new GeoCoordinate(latitude,longitude);
new ReverseGeocodeRequest(vancouver).execute(new ResultListener<Location>() {
#Override
public void onCompleted(Location location, ErrorCode errorCode) {
try {
assetData.address = location.getAddress().toString().replace("\n", "");
} catch (NullPointerException e) {
e.printStackTrace();
}
}
});
You have to make use of NMAAddress class in the HERE iOS SDK.
NMAAddress provides textual address information including house number, street name, city, country, district and more. It encompasses everything about an address or a point on the map. The NMAPlaceLocation class represents an area on the map where additional attributes can be retrieved. These additional attributes include NMAAddress, unique identifier, label, location, access locations, and NMAGeoBoundingBox for the location
Please check the document section Geocoding and Reverse Geocoding for more details including sample codes.
You need to use Google API to fetch address from Geo Coordinates, for that please use following code
func getAddressFromLatLong(latitude: Double, longitude : Double) {
let url = "https://maps.googleapis.com/maps/api/geocode/json?latlng=\(latitude),\(longitude)&key=YOUR_API_KEY_HERE"
Alamofire.request(url).validate().responseJSON { response in
switch response.result {
case .success:
let responseJson = response.result.value! as! NSDictionary
if let results = responseJson.object(forKey: "results")! as? [NSDictionary] {
if results.count > 0 {
if let addressComponents = results[0]["address_components"]! as? [NSDictionary] {
self.address = results[0]["formatted_address"] as? String
for component in addressComponents {
if let temp = component.object(forKey: "types") as? [String] {
if (temp[0] == "postal_code") {
self.pincode = component["long_name"] as? String
}
if (temp[0] == "locality") {
self.city = component["long_name"] as? String
}
if (temp[0] == "administrative_area_level_1") {
self.state = component["long_name"] as? String
}
if (temp[0] == "country") {
self.country = component["long_name"] as? String
}
}
}
}
}
}
case .failure(let error):
print(error)
}
}
}
Here's the code:
CLGeocoder().reverseGeocodeLocation(myCurrentLoc) { (array, err) in
print(err)
if err != nil {
print(err)
return
}
if let place = array?.first {
if let locality = place.locality {
if let country = place.country {
if let addr = place.addressDictionary {
print(addr)
}
let myAddress = "\(locality) \(country)"
self.adress.text = myAddress
self.locMgr.stopUpdatingLocation()
}
}
}
self.locMgr.stopUpdatingLocation()
}
So I can read longitude/latitude, and the only thing I need is the full address. Right now I can print out "New York, US", I find all the information I need is in place.addressDictionary, which is a dictionary. But I can't seem to read anything from that dictionary using normal syntax. How do I do it?
For US address you can use this:
let address = [
place.name,
place.locality,
place.administrativeArea,
place.postalCode,
place.country
]
let formattedAddress = address.flatMap({ $0 }).joined(separator: ", ")
Address is an array of components that are used to compose typical postal address. It can contain nil for some items.
FormattedAddress removes the nils and adds delimiter, I used coma here.
I usually make a location manager helper to fetch details and use a placemark
The dictionary you are talking about comes with placemarks
Here is my Model
class UserLocation: NSObject {
var latitude : String?
var longitude: String?
var timeZone: String?
var city : String?
var state : String?
var country : String?
// you can and more fields of placemark
required init(latitude:String?,longitude:String?,timezone:String?,city:String?,state:String?,country:String?) {
self.latitude = latitude
self.longitude = longitude
self.timeZone = timezone
self.city = city
self.state = state
self.country = country
}
}
And then I use the below code to reverse geocode after getting a location from Location Manager and use placemarks to get details
//Location Manager delegate
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
manager.stopUpdatingLocation()
CLGeocoder().reverseGeocodeLocation(manager.location!, completionHandler: {(placemarks, error)->Void in
if (error != nil) {
print("Reverse geocoder failed with error" + error!.localizedDescription)
return
}
if placemarks!.count > 0 {
let pm = placemarks![0]
if let location = manager.location {
let userLoc = UserLocation(latitude: String(location.coordinate.latitude), longitude: String(location.coordinate.longitude), timezone: NSTimeZone.localTimeZone().name, city: pm.locality!, state: pm.administrativeArea!, country:pm.country!)
//By printing this dictionary you will get the complete address
print(pm.addressDictionary)
print(userLoc.city!)
print(userLoc.state!)
print(userLoc.country!)
print(userLoc.latitude!)
print(userLoc.longitude!)
print(userLoc.timeZone!)
} else {
//Handle error
}
if(!CLGeocoder().geocoding){
CLGeocoder().cancelGeocode()
}
}else{
print("Problem with the data received from geocoder")
}
}
Also you can fetch other properties of placemark like
I also printed the addressDictionary and you can see the complete address in the console in the last image
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
}
}
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.