Could not cast value of type 'NSTaggedPointerString' to 'NSArray in Google Place Api - ios

i integrated Google Place Api to fetch user location, i have to fetch "area","state" and "City" while i try to get value my App gets crash. Please post ur answer
here my sample code
func mapView(mapView: GMSMapView, idleAtCameraPosition position: GMSCameraPosition)
{
dispatch_async(dispatch_get_main_queue(), { () -> Void in
let centerLocation = CLLocation(latitude: position.target.latitude, longitude: position.target.longitude)
CLGeocoder().reverseGeocodeLocation(centerLocation, completionHandler:
{(placemarks, error) in
if (error == nil && placemarks!.count>0)
{
let placemark = CLPlacemark(placemark: placemarksArray[0] as! CLPlacemark)
let latitude = String(format: "%.8f",position.target.latitude)
let longitude = String(format: "%.8f",position.target.longitude)
if let addrList = placemark.addressDictionary
{
print("address==\(address)")
let addStr = address?["FormattedAddressLines"] as! [String]
let addStr1 = address?["City"] as! [String]
let addStr2 = address?["State"] as! [String]
let addStr3 = address?["SubLocality"] as! [String]
NSLog("%#\n%#\n%#", addStr1,addStr2,addStr3)
self.addressLabel.text = addStr.joinWithSeparator(",")
}
}
})
})
}
my output :
address=={
City = "New Delhi";
Country = India;
CountryCode = IN;
FormattedAddressLines = (
"Mayur Vihar",
"New Delhi",
"Delhi 110092",
India
);
Name = 110092;
State = Delhi;
SubAdministrativeArea = Delhi;
SubLocality = "Mayur Vihar";
ZIP = 110092;
}
and my Crash Report :
Could not cast value of type 'NSTaggedPointerString' (0x1064cb860) to 'NSArray' (0x1064cb900).

City and State has String value and FormattedAddressLines has Arry.
Use like this
let addStr1 = address["City"] as! String
let addStr2 = address["State"] as! String
let FormattedAddressLines = address["FormattedAddressLines"] as![String]

Related

UIlabel() tap to expand text

So I am building a map that provides factual data on a variety of points located in Washington DC. The data is getting pulled from a geojson and listed as 20 or so points. When you click on the point, there is a popup that occurs that shows a truncated version of the text, only 10 lines. However some of the text is really long, 200 lines+. I want to develop a way to expand the text, or have a scrollable bar. Basically anything to have the option to open up the text. Adding an image to show what it looks like.
You can see an image here: https://i.stack.imgur.com/yeeBa.jpg
Here is the code I am using.
The data is getting called in the ViewController.swift using this
func loadInitialData() {
// 1
guard let fileName = Bundle.main.path(forResource: "PublicArt4", ofType: "json")
else { return }
let optionalData = try? Data(contentsOf: URL(fileURLWithPath: fileName))
guard
let data = optionalData,
// 2
let json = try? JSONSerialization.jsonObject(with: data),
// 3
let dictionary = json as? [String: Any],
// 4
let works = dictionary["data"] as? [[Any]]
else { return }
// 5
let validWorks = works.compactMap { Artwork(json: $0) }
artworks.append(contentsOf: validWorks)
}
}
The artworkViews.swift displays the label formatting
let detailLabel = UILabel()
detailLabel.text = artwork.locationlink
detailLabel.text = artwork.subtitle
detailCalloutAccessoryView = detailLabel
detailLabel.font = UIFont(name: "Heiti TC", size: 12)
detailLabel.numberOfLines = 10
//detailLabel.adjustsFontSizeToFitWidth = true
// detailLabel.minimumScaleFactor = 0.5
// detailLabel.baselineAdjustment = .alignCenters
// detailLabel.textAlignment = .left
The geojson is formatted like this
{"data" : [ [
1,
"Washington Monument",
"Click here to learn more",
"Madison Dr NW & 15th St NW",
"Washington",
"DC",
20001,
"38.89013",
"-77.033031",
"www.google.com Welcome to the Washington Monument,",
"Mural",
"Some text, not sure what it is",
" "
],...
The Artwork.swift pulls the data in such a way
class Artwork: NSObject, MKAnnotation {
let title: String?
let locationName: String
let locationURL: String
let discipline: String
let coordinate: CLLocationCoordinate2D
init(title: String, locationName: String, locationURL: String, discipline: String, coordinate: CLLocationCoordinate2D) {
self.title = title
self.locationName = locationName
self.locationURL = locationURL
self.discipline = discipline
self.coordinate = coordinate
super.init()
}
var subtitle: String? {
return locationName
}
var locationlink: String? {
return locationURL
}
init?(json: [Any]) {
// 1
if let title = json[2] as? String {
self.title = title
} else {
self.title = "No Title"
}
// json[11] is the long description
//self.locationName = json[11] as! String
// json[12] is the short location string
self.locationName = json[9] as! String
self.discipline = json[10] as! String
self.locationURL = json[3] as! String
// 2
if let latitude = Double(json[7] as! String),
let longitude = Double(json[8] as! String) {
self.coordinate = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
} else {
self.coordinate = CLLocationCoordinate2D()
}
}
// pinTintColor for disciplines: Sculpture, Plaque, Mural, Monument, other
var markerTintColor: UIColor {
switch discipline {
case "Monument":
return .red
case "Mural":
return .cyan
case "Plaque":
return .blue
case "Sculpture":
return .purple
default:
return .green
}
}
var imageName: String? {
if discipline == "Mural" { return "Flag" }
return "Flag"
}
// Annotation right callout accessory opens this mapItem in Maps app
func mapItem() -> MKMapItem {
let addressDict = [CNPostalAddressStreetKey: subtitle!]
let placemark = MKPlacemark(coordinate: coordinate, addressDictionary: addressDict)
let mapItem = MKMapItem(placemark: placemark)
mapItem.name = title
return mapItem
}
}
The one thing I tried was to switch it to a UITextView, but i cant get that to load properly. Mainly because I am not sure how to integrate it into the ViewController.
let detailLabel = UITextView()
detailLabel.text = artwork.locationlink
detailLabel.text = artwork.subtitle
detailLabel.textColor = UIColor.red
detailLabel.selectedTextRange = detailLabel.textRange(from: detailLabel.beginningOfDocument, to: detailLabel.beginningOfDocument)
```

Strange behaviour in showing annotations images on map using data coming from Firebase. SWIFT 4.1

The strange behaviour is that when I add a new annotation, either tapped or user location, it gets displayed with the right chosen icon. When MapVC load for the first time, the posts retrieved from Firebase have all the same icon, ( the icon name of the latest one posted. If, after posting a new one, I exit mapViewVc to the menuVC and re enter mapViewVC than every icon is displaying the same icon again, now being my previously posted one.
a Few times it happened the the icons were two different icons, randomly chosen.
I don't understand why the coordinates are taken right but the image is not.
The app flow is:
I have a mapView vc where I can either double tap on screen and get coordinate or code user location coordinate via a button and then get to an chooseIconVc where I have all available icons to choose for the annotation. Once I select one, the icon name get passed back in in mapViewVC in unwindHere() that stores icon name into a variable and coordinates into another. In postAlertNotification those variables get posted to Firebase.
In displayAlerts() the data from Firebase gets stored into variables to initialise an annotation and gets added to mapView.
chosen icon:
#IBAction func unwindHere(sender:UIStoryboardSegue) { // data coming back
if let sourceViewController = sender.source as? IconsViewController {
alertNotificationType = sourceViewController.dataPassed
if tapCounter > 0 {
alertNotificationLatitude = String(describing: alertCoordinates.latitude)
alertNotificationLongitude = String(describing: alertCoordinates.longitude)
postAlertNotification() // post new notification to Firebase
} else {
alertCoordinates = self.trackingCoordinates
alertNotificationLatitude = String(describing: self.trackingCoordinates!.latitude)
alertNotificationLongitude = String(describing: self.trackingCoordinates!.longitude)
postAlertNotification() // post new notification to Firebase
}
}
}
than post:
func postAlertNotification() {
// to set next notification id as the position it will have in array ( because first position is 0 ) we use the array.count as value
let latitude = alertNotificationLatitude
let longitude = alertNotificationLongitude
let alertType = alertNotificationType
let post: [String:String] = [//"Date" : date as! String,
//"Time" : time as! String,
"Latitude" : latitude as! String,
"Longitude" : longitude as! String,
"Description" : alertType as! String]
var ref: DatabaseReference!
ref = Database.database().reference()
ref.child("Community").child("Alert Notifications").childByAutoId().setValue(post)
}
retrieve and display:
func displayAlerts() {
ref = Database.database().reference()
databaseHandle = ref?.child("Community").child("Alert Notifications").observe(.childAdded, with: { (snapshot) in
// defer { self.dummyFunctionToFoolFirebaseObservers() }
guard let data = snapshot.value as? [String:String] else { return }
guard let firebaseKey = snapshot.key as? String else { return }
// let date = data!["Date"]
// let time = data!["Time"]
let dataLatitude = data["Latitude"]!
let dataLongitude = data["Longitude"]!
self.alertIconToDisplay = data["Description"]!
let doubledLatitude = Double(dataLatitude)
let doubledLongitude = Double(dataLongitude)
let recombinedCoordinate = CLLocationCoordinate2D(latitude: doubledLatitude!, longitude: doubledLongitude!)
print("Firebase post retrieved !")
print("Longitude Actual DataKey is \(String(describing: firebaseKey))")
print("fir long \((snapshot.value!, snapshot.key))")
self.userAlertAnnotation = UserAlert(type: self.alertIconToDisplay!, coordinate: recombinedCoordinate, firebaseKey: firebaseKey)
self.mapView.addAnnotation(self.userAlertAnnotation)
})
}
and
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let annotationView = MKAnnotationView(annotation: userAlertAnnotation, reuseIdentifier: "") // CHANGE FOR NEW ANNOTATION : FULL DATA
//added if statement for displaying user location blue dot
if annotation is MKUserLocation{
return nil
} else {
annotationView.image = UIImage(named: alertIconToDisplay!) // choose the image to load
let transform = CGAffineTransform(scaleX: 0.27, y: 0.27)
annotationView.transform = transform
return annotationView
}
}
the variables declarations :
var alertIconToDisplay: String?
var userAlertAnnotation: UserAlert!
var alertNotificationType: String?
var alertNotificationLatitude: String?
var alertNotificationLongitude: String?
UPDATE:
annotation cLass:
import MapKit
class UserAlert: NSObject , MKAnnotation {
var type: String?
var firebaseKey: String?
var coordinate = CLLocationCoordinate2D()
var image: UIImage?
override init() {
}
init(type:String, coordinate:CLLocationCoordinate2D, firebaseKey: String) {
self.type = type
self.firebaseKey = firebaseKey
self.coordinate = coordinate
}
}
After understanding where the problem I was explained how to changed the displayAlert() into
func displayAlerts() { // rajish version
ref = Database.database().reference()
databaseHandle = ref?.child("Community").child("Alert Notifications").observe(.childAdded, with: { (snapshot) in
// defer { self.dummyFunctionToFoolFirebaseObservers() }
guard let data = snapshot.value as? [String:String] else { return }
guard let firebaseKey = snapshot.key as? String else { return }
// let date = data!["Date"]
// let time = data!["Time"]
let dataLatitude = data["Latitude"]!
let dataLongitude = data["Longitude"]!
let type = data["Description"]!
let id = Int(data["Id"]!)
let doubledLatitude = Double(dataLatitude)
let doubledLongitude = Double(dataLongitude)
let recombinedCoordinate = CLLocationCoordinate2D(latitude: doubledLatitude!, longitude: doubledLongitude!)
print("Firebase post retrieved !")
print("Longitude Actual DataKey is \(String(describing: firebaseKey))")
print("fir long \((snapshot.value!, snapshot.key))")
var userAlertAnnotation = UserAlert(type: type, coordinate: recombinedCoordinate, firebaseKey: firebaseKey, title: type,id: id!)
self.userAlertNotificationArray.append(userAlertAnnotation) // array of notifications coming from Firebase
print("user alert array after append from Firebase is : \(self.userAlertNotificationArray)")
self.alertNotificationArray.append(recombinedCoordinate) // array for checkig alerts on route
self.mapView.addAnnotation(userAlertAnnotation)
})
}
and the mapView to:
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? { // rajish version
let annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: "")
if annotation is MKUserLocation{
return nil
} else {
print(annotation.coordinate)
annotationView.image = UIImage(named:(annotationView.annotation?.title)! ?? "")
// annotationView.canShowCallout = true
let transform = CGAffineTransform(scaleX: 0.27, y: 0.27)
annotationView.transform = transform
return annotationView
}
}
that solved it.

SWIFT 4.1 Cannot invoke initializer for type 'Double' with an argument list of type '(String?)'

I'm retrieving mapView annotations posted in Firebase to show them on map, but while converting String values for latitude and longitude to recombine them into CLLocationCoordinates2D I get the error. I don't understand why, because in another function I use the same method but getting the values from arrays but I don't get the error. Also on retrieving the data I would like to also use the key value from firebase as initialiser for my annotations. But I get two more errors Use of unresolved identifier 'firebaseKey' and Use of unresolved identifier 'recombinedCoordinate' for initialisers. Here're the function:
func displayAlerts() {
// FIREBASE: Reference
ref = Database.database().reference()
// FIREBASE:Retrieve posts and listen for changes
databaseHandle = ref?.child("Community").child("Alert Notifications").observe(.childAdded, with: { (snapshot) in
let data = snapshot.value as? [String:String]
if let actualData = data {
let dataLatitude = data!["Latitude"]
let dataLongitude = data!["Longitude"]
self.alertIconToDisplay = data!["Description"]
let doubledLatitude = Double(dataLatitude)
let doubledLongitude = Double(dataLongitude)
var recombinedCoordinate = CLLocationCoordinate2D(latitude: doubledLatitude!, longitude: doubledLongitude!)
print("Firebase post retrieved !")
self.dummyFunctionToFoolFirebaseObservers()
}
let dataKey = snapshot.key as? String
if let firebaseKey = dataKey {
print("Longitude DataKey is \(String(describing: dataKey))")
print("Longitude Actual DataKey is \(String(describing: firebaseKey))")
self.dummyFunctionToFoolFirebaseObservers()
}
print("fir long \((snapshot.value!, snapshot.key))")
userAlertAnnotation = UserAlert(type: self.alertIconToDisplay, coordinate: recombinedCoordinate, firebaseKey: firebaseKey)
self.mapView.addAnnotation(self.userAlertAnnotation)
})
}
Here's the annotation model :
class UserAlert: NSObject , MKAnnotation {
var type: String?
var firebaseKey: String?
var coordinate:CLLocationCoordinate2D
init(type:String, coordinate:CLLocationCoordinate2D, firebaseKey: String) {
self.type = type
self.firebaseKey = firebaseKey
self.coordinate = coordinate
}
}
What am I doing wrong here? I understand that the error on the initialisers are because initialisation occurs in key closures, but how then I incorporate all data into initialiser ?
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let annotationView = MKAnnotationView(annotation: userAlertAnnotation, reuseIdentifier: "") // CHANGE FOR NEW ANNOTATION : FULL DATA
//added if statement for displaying user location blue dot
if annotation is MKUserLocation{
return nil
} else {
annotationView.image = UIImage(named: alertIconToDisplay!) // choose the image to load
let transform = CGAffineTransform(scaleX: 0.27, y: 0.27)
annotationView.transform = transform
return annotationView
}
}
func postAlertNotification() {
// to set next notification id as the position it will have in array ( because first position is 0 ) we use the array.count as value
let latitude = alertNotificationLatitude
let longitude = alertNotificationLongitude
let alertType = alertNotificationType
let post: [String:String] = [//"Date" : date as! String,
//"Time" : time as! String,
"Latitude" : latitude as! String,
"Longitude" : longitude as! String,
"Description" : alertType as! String]
var ref: DatabaseReference!
ref = Database.database().reference()
ref.child("Community").child("Alert Notifications").childByAutoId().setValue(post)
}
The error in the topic says that you can't create a Double from an optional String which is true.
To solve it force unwrap the values for Latitude and Longitude.
But the main issue is a scope issue, all variables used in the initializer must be in the same scope. You can flatten the scope with guard statements:
...
databaseHandle = ref?.child("Community").child("Alert Notifications").observe(.childAdded, with: { (snapshot) in
defer { self.dummyFunctionToFoolFirebaseObservers() }
guard let data = snapshot.value as? [String:String] else { return }
guard let firebaseKey = snapshot.key as? String else { return }
// let date = data!["Date"]
// let time = data!["Time"]
let dataLatitude = data["Latitude"]!
let dataLongitude = data["Longitude"]!
self.alertIconToDisplay = data["Description"]!
let doubledLatitude = Double(dataLatitude)
let doubledLongitude = Double(dataLongitude)
let recombinedCoordinate = CLLocationCoordinate2D(latitude: doubledLatitude!, longitude: doubledLongitude!)
print("Firebase post retrieved !")
// self .keyaLon = dataKey
// self.keyaLonArray.append(firebaseKey)
print("Longitude Actual DataKey is \(String(describing: firebaseKey))")
print("fir long \((snapshot.value!, snapshot.key))")
self.userAlertAnnotation = UserAlert(type: self.alertIconToDisplay, coordinate: recombinedCoordinate, firebaseKey: firebaseKey)
self.mapView.addAnnotation(self.userAlertAnnotation)
})

Swift Firebase Sort By Distance

I am trying to sort my array by distance. I already have everything hooked up to grab the distance's but unsure how to sort from closest to furthest from the users location. I've used the below code for MKMapItem's yet unsure how to apply to my current array.
func sortMapItems() {
self.mapItems = self.mapItems.sorted(by: { (b, a) -> Bool in
return self.userLocation.location!.distance(from: a.placemark.location!) > self.userLocation.location!.distance(from: b.placemark.location!)
})
}
Firebase Call
databaseRef.child("Businesses").queryOrdered(byChild: "businessName").observe(.childAdded, with: { (snapshot) in
let key = snapshot.key
if(key == self.loggedInUser?.uid) {
print("Same as logged in user, so don't show!")
} else {
if let locationValue = snapshot.value as? [String: AnyObject] {
let lat = Double(locationValue["businessLatitude"] as! String)
let long = Double(locationValue["businessLongitude"] as! String)
let businessLocation = CLLocation(latitude: lat!, longitude: long!)
let latitude = self.locationManager.location?.coordinate.latitude
let longitude = self.locationManager.location?.coordinate.longitude
let userLocation = CLLocation(latitude: latitude!, longitude: longitude!)
let distanceInMeters : Double = userLocation.distance(from: businessLocation)
let distanceInMiles : Double = ((distanceInMeters.description as String).doubleValue * 0.00062137)
let distanceLabelText = "\(distanceInMiles.string(2)) miles away"
var singleChildDictionary = locationValue
singleChildDictionary["distanceLabelText"] = distanceLabelText as AnyObject
self.usersArray.append(singleChildDictionary as NSDictionary)
/*
func sortMapItems() {
self.mapItems = self.mapItems.sorted(by: { (b, a) -> Bool in
return self.userLocation.location!.distance(from: a.placemark.location!) > self.userLocation.location!.distance(from: b.placemark.location!)
})
}
*/
}
//insert the rows
self.followUsersTableView.insertRows(at: [IndexPath(row:self.usersArray.count-1,section:0)], with: UITableViewRowAnimation.automatic)
}
}) { (error) in
print(error.localizedDescription)
}
}
First make these changes in your code
singleChildDictionary["distanceInMiles"] = distanceInMiles
Then you can sort it like this:
self.usersArray = self.usersArray.sorted {
!($0["distanceInMiles"] as! Double > $1["distanceInMiles"] as! Double)
}

How to get formatted address NSString from AddressDictionary?

Trying to get formatted address from AddressDictionary, that I got from CLGeocoder.
Used following code with no result:
subtitle = [NSString stringWithString:[[addressDict objectForKey:#"FormattedAddressLines"]objectAtIndex:0]];
Also tried:
subtitle = [[[ABAddressBook sharedAddressBook] formattedAddressFromDictionary:placemark.addressDictionary] string];
but this code seems working on Mac OS X only.
Compiler asks about ABAdressBook, but I have both header files imported.
#import <AddressBook/ABAddressBook.h>
#import <AddressBook/AddressBook.h>
The documentation for the addressDictionary property says:
You can format the contents of this dictionary to get a full address
string as opposed to building the address yourself. To format the
dictionary, use the ABCreateStringWithAddressDictionary function as
described in Address Book UI Functions Reference.
So add and import the AddressBookUI framework and try:
subtitle =
ABCreateStringWithAddressDictionary(placemark.addressDictionary, NO);
After doing some digging under iOS 6.1 I found out that the CLPlacemark address dictionary contains a pre-formatted address:
CLLocation *location = [[CLLocation alloc]initWithLatitude:37.3175 longitude:-122.041944];
[[[CLGeocoder alloc]init] reverseGeocodeLocation:location completionHandler:^(NSArray *placemarks, NSError *error) {
CLPlacemark *placemark = placemarks[0];
NSArray *lines = placemark.addressDictionary[ #"FormattedAddressLines"];
NSString *addressString = [lines componentsJoinedByString:#"\n"];
NSLog(#"Address: %#", addressString);
}];
I couldn't yet find documentation about this, but it works for all the addresses that I tested.
As highlighted by Martyn Davis, ABCreateStringWithAddressDictionary is deprecated in iOS 9.
You can use the functions below to convert the addressDictionary to the newer CNMutablePostalAddress, then use the CNPostalAddressFormatter to generate a localised string as long as you import the Contacts framework.
Swift 3.x
// Convert to the newer CNPostalAddress
func postalAddressFromAddressDictionary(_ addressdictionary: Dictionary<NSObject,AnyObject>) -> CNMutablePostalAddress {
let address = CNMutablePostalAddress()
address.street = addressdictionary["Street" as NSObject] as? String ?? ""
address.state = addressdictionary["State" as NSObject] as? String ?? ""
address.city = addressdictionary["City" as NSObject] as? String ?? ""
address.country = addressdictionary["Country" as NSObject] as? String ?? ""
address.postalCode = addressdictionary["ZIP" as NSObject] as? String ?? ""
return address
}
// Create a localized address string from an Address Dictionary
func localizedStringForAddressDictionary(addressDictionary: Dictionary<NSObject,AnyObject>) -> String {
return CNPostalAddressFormatter.string(from: postalAddressFromAddressDictionary(addressDictionary), style: .mailingAddress)
}
Swift 2.x
import Contacts
// Convert to the newer CNPostalAddress
func postalAddressFromAddressDictionary(addressdictionary: Dictionary<NSObject,AnyObject>) -> CNMutablePostalAddress {
let address = CNMutablePostalAddress()
address.street = addressdictionary["Street"] as? String ?? ""
address.state = addressdictionary["State"] as? String ?? ""
address.city = addressdictionary["City"] as? String ?? ""
address.country = addressdictionary["Country"] as? String ?? ""
address.postalCode = addressdictionary["ZIP"] as? String ?? ""
return address
}
// Create a localized address string from an Address Dictionary
func localizedStringForAddressDictionary(addressDictionary: Dictionary<NSObject,AnyObject>) -> String {
return CNPostalAddressFormatter.stringFromPostalAddress(postalAddressFromAddressDictionary(addressDictionary), style: .MailingAddress)
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
// get the address
if let location = locations.last {
CLGeocoder().reverseGeocodeLocation(location, completionHandler: { (result: [CLPlacemark]?, err: NSError?) -> Void in
if let placemark = result?.last
, addrList = placemark.addressDictionary?["FormattedAddressLines"] as? [String]
{
let address = addrList.joinWithSeparator(", ")
print(address)
}
})
}
}
Above is the swift version.
I am using Swift 3 / XCode 8
ZYiOS's answer was nice and short but did not compile for me.
The question asks how to get from an existing Address Dictionary to a string address. This is what I did:
import CoreLocation
func getAddressString(placemark: CLPlacemark) -> String? {
var originAddress : String?
if let addrList = placemark.addressDictionary?["FormattedAddressLines"] as? [String]
{
originAddress = addrList.joined(separator: ", ")
}
return originAddress
}
Swift 3 / Xcode 8 Helper Mehtod to get address from CLPlaceMark
class func formattedAddress(fromPlacemark 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
}
Now this is as simple as
func updateUserAddress(coordinates: CLLocationCoordinate2D) {
let geoCoder = CLGeocoder()
let location = CLLocation(latitude: coordinates.latitude, longitude: coordinates.longitude)
geoCoder.reverseGeocodeLocation(location) {[weak self] (placemarks, error) in
if error == nil, let placemark = placemarks?.first, let address = placemark.postalAddress {
self?.userLocationLabel.text = CNPostalAddressFormatter.string(from: address, style: .mailingAddress)
}
}
}
iOS 11+
import CoreLocation
import Contacts
public extension CLPlacemark {
func formattedAddress() -> String? {
guard let postalAddress = postalAddress else { return nil }
let formatter = CNPostalAddressFormatter()
formatter.style = .mailingAddress
let formatterString = formatter.string(from: postalAddress)
return formatterString.replacingOccurrences(of: "\n", with: " ")
}
}
Simply create extension for CLLocation:
typealias AddressDictionaryHandler = ([String: Any]?) -> Void
extension CLLocation {
func addressDictionary(completion: #escaping AddressDictionaryHandler) {
CLGeocoder().reverseGeocodeLocation(self) { placemarks, _ in
completion(placemarks?.first?.addressDictionary as? [String: AnyObject])
}
}
}
Example:
let location = CLLocation()
location.addressDictionary { dictionary in
let city = dictionary?["City"] as? String
let street = dictionary?["Street"] as? String
}
Swift 5 version
CLGeocoder().reverseGeocodeLocation(newLocation!, preferredLocale: nil) { (clPlacemark: [CLPlacemark]?, error: Error?) in
guard let place = clPlacemark?.first else {
print("No placemark from Apple: \(String(describing: error))")
return
}
if let addrList = place.addressDictionary?["FormattedAddressLines"] as? [String] {
let addressString = addrList.joined(separator: ", ")
print(addressString)
}
}

Resources