Google Maps iOS Reverse Geocoding - ios

I am having an issue with Google Maps Reverse Geocoding when it comes to setting a UILabel's text to the reverse geocoded address. The UILabel is in an XIB use as a custom infowindow. I have another custom infowindow for other data that is working correctly however, it appears that when I try to set the label's text within the reverse geocode callback / completion handler, it doesn't work. Here is the code I have so far and I have tried the code multiple ways, including assigning it to variables and I have not been able to get anything to work.
let infoWindow = NSBundle.mainBundle().loadNibNamed("InfoWindowCurrent", owner: self, options: nil)[0] as! InfoWindowCurrent
let geocoder = GMSGeocoder()
let coordinate = CLLocationCoordinate2DMake(Double(self.locLatitude)!, Double(self.locLongitude)!)
var currentAddress = String()
geocoder.reverseGeocodeCoordinate(coordinate) { response , error in
if let address = response?.firstResult() {
let lines = address.lines! as [String]
currentAddress = lines.joinWithSeparator("\n")
}
}
infoWindow.labelAddressStreet.text = currentAddress
return infoWindow
I have also tried this:
let infoWindow = NSBundle.mainBundle().loadNibNamed("InfoWindowCurrent", owner: self, options: nil)[0] as! InfoWindowCurrent
let geocoder = GMSGeocoder()
let coordinate = CLLocationCoordinate2DMake(Double(self.locLatitude)!, Double(self.locLongitude)!)
geocoder.reverseGeocodeCoordinate(coordinate) { response , error in
if let address = response?.firstResult() {
let lines = address.lines! as [String]
infoWindow.labelAddressStreet.text = lines.joinWithSeparator("\n")
}
}
return infoWindow
Everything is connected correctly because the following code works:
let infoWindow = NSBundle.mainBundle().loadNibNamed("InfoWindowCurrent", owner: self, options: nil)[0] as! InfoWindowCurrent
let geocoder = GMSGeocoder()
let coordinate = CLLocationCoordinate2DMake(Double(self.locLatitude)!, Double(self.locLongitude)!)
geocoder.reverseGeocodeCoordinate(coordinate) { response , error in
if let address = response?.firstResult() {
let lines = address.lines! as [String]
}
}
infoWindow.labelAddressStreet.text = "Unable to reverse geocode location!"
return infoWindow
Any help is definitely appreciated!

You are using a return out of the braces of the geocoding, for this reason the last code works. You should do something like this:
func getAddress(currentAdd : ( returnAddress :String)->Void){
let infoWindow = NSBundle.mainBundle().loadNibNamed("InfoWindowCurrent", owner: self, options: nil)[0] as! InfoWindowCurrent
let geocoder = GMSGeocoder()
let coordinate = CLLocationCoordinate2DMake(Double(self.locLatitude)!,Double(self.locLongitude)!)
var currentAddress = String()
geocoder.reverseGeocodeCoordinate(coordinate) { response , error in
if let address = response?.firstResult() {
let lines = address.lines! as [String]
currentAddress = lines.joinWithSeparator("\n")
currentAdd(returnAddress: currentAddress)
}
}
}
And call that function
getAddress() { (returnAddress) in
print("\(returnAddress)")
}

So, I ended up going another route. It's not pretty, but it works. Each GMSMarker has a "userData" attribute that can be used to pass data. What I did was moved the marker creation into the reverse geocode completion handler and assigned the address to the "userData" attribute. Then, when the user taps to show the current address, the reverse geocode is kicked off, the marker is created and placed on the map.
geocoder.reverseGeocodeCoordinate(position) { response, error in
if let location = response?.firstResult() {
let marker = GMSMarker(position: position)
let lines = location.lines! as [String]
marker.userData = lines.joined(separator: "\n")
marker.title = lines.joined(separator: "\n")
marker.infoWindowAnchor = CGPoint(x: 0.5, y: -0.25)
marker.accessibilityLabel = "current"
marker.map = self.mapView
self.mapView.animate(toLocation: position)
self.mapView.selectedMarker = marker
}
}
And when the marker is selected, the label is set to the address as passed in the "userData" attribute:
let infoWindow = Bundle.main.loadNibNamed("InfoWindowCurrent", owner: self, options: nil)?[0] as! InfoWindowCurrent
infoWindow.labelAddress.text = marker.userData as? String
return infoWindow

The first response worked for me. As an alternative to
marker.title = lines.joined(separator: "\n")`
try this, it will give you the street address:
marker.title = response?.firstResult()?.thoroughfare
marker.snippet = response?.firstResult()?.locality
For the city, try
marker.snippet = response?.firstResult()?.locality

Related

Maps Direction Line doesn't showing up on Maps

On trying to give direction line between User location to defined location. The pinpoints were right and exactly at the desired location but there is no 'Line' direction which leads User location to defined location.
It was done like this image:
and my code for drawing the maps along with parsing google maps API JSON:
func DrawDirection(url: String, cord: CLLocationCoordinate2D){
request(url).responseJSON { response in
let parsed = try? JSONSerialization.jsonObject(with: response.data!, options: []) as! [String:Any]
let routes = parsed!["routes"] as! [[String:Any]]
for route in routes {
let dictArr = route["legs"] as? [[String:Any]]
let dict = dictArr![0]["steps"] as? [[String:Any]]
let start = dict![0]["start_location"] as? [String:Any]
let lat = start!["lat"] as! Double
let long = start!["lng"] as! Double
let dotcoordinate = CLLocationCoordinate2D(latitude: CLLocationDegrees(lat), longitude: CLLocationDegrees(long))
let routePoly = route["overview_polyline"] as! [String:Any]
let points = routePoly["points"] as! String
let line = points
DispatchQueue.main.async {
let bounds = GMSCoordinateBounds(coordinate: cord, coordinate: dotcoordinate)
let update = GMSCameraUpdate.fit(bounds, with: UIEdgeInsetsMake(170, 30, 30, 30))
self.CinemaLoc.moveCamera(update)
}
self.AddingPolyLine(encString: line, dir: cord, dot: dotcoordinate)
}
}
}
func AddingPolyLine(encString: String, dir: CLLocationCoordinate2D, dot: CLLocationCoordinate2D){
let dotpath: GMSMutablePath = GMSMutablePath()
dotpath.add(CLLocationCoordinate2DMake(dir.latitude, dir.longitude)) //original loc
dotpath.add(CLLocationCoordinate2DMake(dot.latitude, dot.longitude)) //starting points
let dotpoly = GMSPolyline(path: dotpath)
dotpoly.map = self.CinemaLoc
dotpoly.strokeWidth = 3.0
let styles:[Any] = [GMSStrokeStyle.solidColor(.blue), GMSStrokeStyle.solidColor(.clear)]
let lengths:[Any] = [10,5]
dotpoly.spans = GMSStyleSpans(dotpoly.path!,styles as!
[GMSStrokeStyle], lengths as! [NSNumber], GMSLengthKind.rhumb)
let poly = GMSPolyline(path: dotpath)
poly.strokeWidth = 3.0
poly.strokeColor = .red
poly.map = self.CinemaLoc
}
cord is User location
been trying all possible way on stack overflow but not getting any change and sometimes there are log saying "Failed to load optimized model at path ~/GoogleMaps.bundle/GMSCacheStorage.momd/"
is there any wrong with my code?
Path should be created only once, for a specific path, and then you can add coordinates to the path. You're doing wrong by putting let dotpath: GMSMutablePath = GMSMutablePath() inside your method call, which gets called for every loop.
Sorry guys, problem fixed. there are misplaced on my url. and for adding polyline should be like this
let path = GMSMutablePath(fromEncodedPath: "String from overview json")
let poly = GMSPolyline(path: dotpath)
poly.strokeWidth = 3.0
poly.strokeColor = .red
poly.map = self.CinemaLoc
On routes loop, it should be no problem since google maps api only give 1 array of route, but for better practice. It should out of loop
Thank you and happy coding!

How to Make an Array for Annotations

Not all my annotations will show up in my map because I can't make var annotation = MKPoinatAnnotation the same MKPointAnnotation in fetching the CKrecords(annotation), and in getting directions for an annotation. I'm confused on how to make an array for annotations so I can be able to load all my annotations from the CloudKit database and be able to get directions when the annotation is selected.
let annotation = MKPointAnnotation()
let database = CKContainer.default().publicCloudDatabase
var truck: [CKRecord] = []
func fetch() {
let truePredicate = NSPredicate(value: true)
let eventQuery = CKQuery(recordType: "User", predicate: truePredicate)
let queryOperation = CKQueryOperation(query: eventQuery)
queryOperation.recordFetchedBlock = { (record : CKRecord!) in
self.truck.append(record)
self.annotation.title = record["username"] as? String
self.annotation.subtitle = record["hours"] as? String
if let location = record["location"] as? CLLocation {
self.annotation.coordinate = location.coordinate
}
print("recordFetchedBlock: \(record)")
self.mapView.addAnnotation(self.annotation)
}
self.database.add(queryOperation)
}
How I get directions -
#IBAction func getDirections(_ sender: Any) {
let view = annotation.coordinate
print("Annotation: \(String(describing: view ))")
let currentLocMapItem = MKMapItem.forCurrentLocation()
let selectedPlacemark = MKPlacemark(coordinate: view, addressDictionary: nil)
let selectedMapItem = MKMapItem(placemark: selectedPlacemark)
let mapItems = [selectedMapItem, currentLocMapItem]
let launchOptions = [MKLaunchOptionsDirectionsModeKey: MKLaunchOptionsDirectionsModeDriving]
MKMapItem.openMaps(with: mapItems, launchOptions:launchOptions)
}
I'm assuming the contents of the array change depending on the search query. If that is the case, make an array of type MKPointAnnotation:
var points: [MKPointAnnotation] = []
Then fill the array however you fill it and run it through a loop whereby each iteration adds a point to the map:
for point in points {
mapView.addAnnotation(point)
}
If you have a problem with varying types of annotations, make the array of type CLLocationCoordinate2D and then you can fill the array by accessing MKPointAnnotation.coordinate, for example.
Does this help?

Custom info window for selected place not showing up (Google Maps/Places API)

I'm using the Google Places API, and have implemented their pickPlace function which looks like this:
func pickPlace() {
let center = CLLocationCoordinate2D(latitude: 40.708637, longitude: -74.014839)
let northEast = CLLocationCoordinate2D(latitude: center.latitude + 0.001, longitude: center.longitude + 0.001)
let southWest = CLLocationCoordinate2D(latitude: center.latitude - 0.001, longitude: center.longitude - 0.001)
let viewport = GMSCoordinateBounds(coordinate: northEast, coordinate: southWest)
let config = GMSPlacePickerConfig(viewport: viewport)
let placePicker = GMSPlacePicker(config: config)
placePicker.pickPlace(callback: {(place, error) -> Void in
if let error = error {
print("Pick Place error: \(error.localizedDescription)")
return
}
if let place = place {
// Set place to class variable selectedPlace
self.selectedPlace = place
// Add marker & move camera to new place
if self.selectedPlace != nil {
let cam = GMSCameraPosition.camera(withTarget: (self.selectedPlace?.coordinate)!, zoom: 18)
let mapView = GMSMapView.map(withFrame: CGRect.zero, camera: cam)
let newPlaceMarker = GMSMarker(position: (self.selectedPlace?.coordinate)!)
newPlaceMarker.map = mapView
self.navigationItem.title = self.selectedPlace?.name
print(self.selectedPlace?.website)
newPlaceMarker.title = self.selectedPlace?.name
self.view = mapView
}
} else {
self.navigationItem.title = "No place selected"
}
})
}
So this allows the user to search for a place like "Starbucks" and sets place to the variable var selectedPlace: GMSPlace? (declared at class scope): the map moves there, adds a marker at that location, changes the navigationItem.title to the selectedPlace.name, and prints the website to the console - all good so far.
Now I'd like an info window to pop up when the marker is tapped, like I've done with some other default markers that I've hardcoded. Here's the infoWindow function:
func mapView(_ mapView: GMSMapView, markerInfoWindow marker: GMSMarker) -> UIView? {
let customInfoWindow = Bundle.main.loadNibNamed("CustomInfoWindow", owner: self, options: nil)?[0] as! CustomInfoWindow
let placeName = marker.title!
switch placeName {
case "TGI Friday's":
customInfoWindow.nameLbl.text = "TGI Friday's"
customInfoWindow.detailLabel.text = "Thoroughly Average Chain Restaurant"
customInfoWindow.placeImage.image = UIImage(named: "fridays")
self.navigationItem.title = "TGI Friday's"
case "George's":
customInfoWindow.nameLbl.text = "George's"
customInfoWindow.detailLabel.text = "Old School Diner"
customInfoWindow.placeImage.image = UIImage(named: "georges")
self.navigationItem.title = "George's"
case "Reserve Cut":
customInfoWindow.nameLbl.text = "Reserve Cut"
customInfoWindow.detailLabel.text = "Kosher Steakhouse"
customInfoWindow.placeImage.image = UIImage(named: "reserveCut")
self.navigationItem.title = "Reserve Cut"
case "O'Hara's":
customInfoWindow.nameLbl.text = "O'Hara's"
customInfoWindow.detailLabel.text = "Irish Pub"
customInfoWindow.placeImage.image = UIImage(named: "oharas")
self.navigationItem.title = "O'Hara's"
case "Bill's Bar & Burger":
customInfoWindow.nameLbl.text = "Bill's Bar & Burger"
customInfoWindow.detailLabel.text = "Bar founded by Bill that also has burgers"
customInfoWindow.placeImage.image = UIImage(named: "bills")
self.navigationItem.title = "Bill's Bar & Burger"
default:
customInfoWindow.nameLbl.text = self.selectedPlace?.name
customInfoWindow.detailLabel.text = self.selectedPlace?.formattedAddress
customInfoWindow.placeImage.image = UIImage(named: "noImageFound")
self.navigationItem.title = self.selectedPlace?.name
}
return customInfoWindow
}
My custom window pops up fine when I tap on my hardcoded default markers, but not with the user-selected place. The title pops up in the default info window but not my custom window. Does anybody know what's going on and how I can get this to work?
EDIT: If I print(selectedPlace?.website) in a different function, it's nil. Not sure why it works fine within the pickPlace function but not outside - that's why I set self.selectedPlace = place, so I could use it around my class.
Anyway that's what's causing the issue at hand I think, but I don't know why or how to fix it. So if anyone can help me fix it you're awesome.

Can't get placemark out of reverseGeocodeLocation func Swift 3

I'm using the function reverseGeocodeLocation to turn coordinates (which I use for pinpoints) to turn into an address.
I've come up with this code:
func displayMarkers(/*completion: #escaping (CLPlacemark!)->()*/)
{
let annotationView = MKAnnotationView()
var integerCount = 0
let detailButton: UIButton = UIButton(type: .detailDisclosure)
annotationView.rightCalloutAccessoryView = detailButton
let geoCoder = CLGeocoder()
getFromDatabase { (locs) in
// Hier is "locs" de [CLLocationCoordinate2D] array
for location in locs{
let loca = CLLocation(latitude: location.latitude, longitude: location.longitude)
geoCoder.reverseGeocodeLocation(loca){placemarks, error in
var placemark : CLPlacemark!
placemark = placemarks?[0]
//let streetname = (placemark.addressDictionary?["Street"])
//let city = (placemark.addressDictionary?["City"])
//let cityAndStreet = "\(streetname!) \(city!)"
//completion(placemark)
}
//self.displayMarkers { (allPlacemarks) in
//let streetname = (allPlacemarks.addressDictionary?["Street"])
//let city = (allPlacemarks.addressDictionary?["City"])
//let cityAndStreet = "\(streetname!) \(city!)"
let annotation = MKPointAnnotation()
annotation.coordinate = location
annotation.title = "Taxi \(integerCount)"
annotation.subtitle = ""
self.mapView.addAnnotation(annotation)
}
integerCount = integerCount + 1
}
}
My question is, I can't get the completion working (so I commented it out).
When I use this completion like this, I get an error in my viewdidload where I call displayMarkers(), the error says I need to put in an argument which I don't have.
Is there any other way how I can get usable information out of it so I can put the address at annotation.subtitle ? I would be really glad to here it!
Try this,
fix
func displayMarkers(completion: #escaping (CLPlacemark!)->()) {}
to
func displayMarkers(completion: #escaping (_ placemark:CLPlacemark)->()) {}
if it wroks, however, you might better guard the error must to be nil, than you can get the placemarks safely.

How do I iterate through JSON co-ordinates and build annotations as one function?

I am struggling to get the annotations being placed using JSON data. I have tried iterating the coordinates from the JSON into a new array but when I try pass an array to where I need the coordinates it fails because it cannot take arrays. How can I fix this?
Can anyone help?
Alamofire.request(.GET, "https://demo1991046.mockable.io/score/locations").responseJSON { (responseData) -> Void in
let swiftyJsonVar = JSON(responseData.result.value!)
if let resData = swiftyJsonVar["users"].arrayObject as? [NSArray] {
self.newArray = (resData as? [NSArray])
}
print("\([self.newArray])")
for var i = 0; i < self.newArray!.count; ++i {
self.longitude.append(self.newArray[i]["lon"] as! String!)
print("longitude: \(self.longitude)")
self.latitude.append(self.newArray[i]["lat"] as! String!)
print("latitude: \(self.latitude)")
}
let doubleLat = self.latitude.map {
Double(($0 as NSString).doubleValue)
}
let doubleLon = self.longitude.map {
Double(($0 as NSString).doubleValue)
}
print("doublelat: \(doubleLat)")
print("doubleLon: \(doubleLon)")
// 1
self.locationManager.delegate = self
// 2
self.locationManager.requestAlwaysAuthorization()
// 3
let theSpan:MKCoordinateSpan = MKCoordinateSpanMake(0.01 , 0.01)
let location:CLLocationCoordinate2D = CLLocationCoordinate2D(latitude: doubleLat, longitude: doubleLon) // <- here is where I get an error: "Cannot convert value of type '[Double]' to expect argument type 'CLLocationDegrees' (aka 'Double")"
// print("lat: \((locationManager.location?.coordinate.latitude)!)")
// print("lon: \((locationManager.location?.coordinate.longitude)!)")
let theRegion:MKCoordinateRegion = MKCoordinateRegionMake(location, theSpan)
self.mapView.setRegion(theRegion, animated: true)
let anotation = MKPointAnnotation()
anotation.coordinate = location
anotation.title = "The Location"
anotation.subtitle = "This is the location !!!"
self.mapView.addAnnotation(anotation)
}
}
I have done soem modifies below to your code
Didn't convert the json to NSArray (by using .array instead of .arrayObject)
moved adding anotation to the map inside the for loop to add all of them.
Moved setting a region to the map out side the for loop and left it to you to set the location you like.
Alamofire.request(.GET, "https://demo1991046.mockable.io/score/locations").responseJSON { (responseData) -> Void in
let swiftyJsonVar = JSON(responseData.result.value!)
// get the users from the json var, no need to convert it to Array
guard let usersJsonArray = swiftyJsonVar["users"].array else {
// users not found in the json
return
}
// the usersJsonArray is array of json which will be much easier for work with.
// No need for 1,2 and 3 to be in the for loop.
// 1
self.locationManager.delegate = self
// 2
self.locationManager.requestAlwaysAuthorization()
// 3
let theSpan:MKCoordinateSpan = MKCoordinateSpanMake(0.01 , 0.01)
for userJson in usersJsonArray {
let longitudeString = userJson["lon"].stringValue
print("longitude: \(longitudeString)")
let latitudeString = userJson["lat"].stringValue
print("latitude: \(latitudeString)")
let doubleLat = Double(latitudeString)
let doubleLon = Double(longitudeString)
print("doublelat: \(doubleLat)")
print("doubleLon: \(doubleLon)")
// by having the next code block inside the for loop you will be able to add all the user locations to the map as anotations.
let location:CLLocationCoordinate2D = CLLocationCoordinate2D(latitude: doubleLat, longitude: doubleLon) // Now should work fine
let anotation = MKPointAnnotation()
anotation.coordinate = location
anotation.title = "The Location"
anotation.subtitle = "This is the location !!!"
self.mapView.addAnnotation(anotation)
} // for usersJson
// you need to figure out the loaction you will set for the mapView region.
let location = .... // set the location you like.
let theRegion:MKCoordinateRegion = MKCoordinateRegionMake(location, theSpan)
self.mapView.setRegion(theRegion, animated: true)
}

Resources