I have a mapview and I added a method to drop a pin on the location where the user had pressed. The callout shows the address of the location as shown on the image.
screenshot of my mapview with annotation pin and callout view.
And my code is as following:
func onTapGestureRecognized(sender: UILongPressGestureRecognizer) {
self.mapView.removeAnnotations(mapView.annotations)
let location = tapRecognizer.location(in: mapView)
let coordinate = mapView.convert(location,toCoordinateFrom: mapView)
let getLat: CLLocationDegrees = coordinate.latitude
let getLon: CLLocationDegrees = coordinate.longitude
let theLocation: CLLocation = CLLocation(latitude: getLat, longitude: getLon)
let geoCoder = CLGeocoder()
geoCoder.reverseGeocodeLocation(theLocation, completionHandler: { (placemarks, error) -> Void in
// Place details
var placeMark: CLPlacemark!
placeMark = placemarks?[0]
var theLocationName = ""
var theStreetNumber = ""
var theStreet = ""
var theCity = ""
var theZip = ""
var theCountry = ""
// Address dictionary
print(placeMark.addressDictionary as Any)
// Location name
if let locationName = placeMark.name{
theLocationName = locationName
}
if let streetNumber = placeMark.subThoroughfare{
theStreetNumber = streetNumber
}
// Street address
if let street = placeMark.thoroughfare {
theStreet = street
}
// City
if let city = placeMark.locality {
theCity = city
}
// Zip code
if let zip = placeMark.postalCode{
theZip = zip
}
// Country
if let country = placeMark.isoCountryCode{
theCountry = country
}
let annotation = MKPointAnnotation()
annotation.title = theLocationName
annotation.subtitle = theStreetNumber + " " + theStreet + ", " + theCity + ", " + theCountry + ", " + theZip
if let location = placeMark.location {
annotation.coordinate = location.coordinate
// Display the annotation
self.mapView.showAnnotations([annotation], animated: true)
}
})
}
As you can see, when I try to get the location name by calling the line (((( if let locationName = placeMark.name )))), I can only get the address: "5197 Yonge St", instead of the restaurant name : " Pho 88 Restaurant ".
Can anyone tell me where I did wrong? or is it simply cannot be achieved? Thanks!
I can't give you a complete answer, but I may be able to point you in the right direction. As far as I can see, you will only ever get a single entry returned for placemarks, but you can get a more complete list using MKLocalSearchRequest. the challenge is going to be how you match up the returned values to exactly which one you want - maybe you have to ask the user to select from a short list? Also, I think you need to specify which type of establishment you're searching for. Here's something you could include within your completion handler above
let request = MKLocalSearchRequest()
request.naturalLanguageQuery = "restaurant" // or whatever you're searching for
request.region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: getLat, longitude: getLon), span: self.mapView.region.span)
let search = MKLocalSearch(request: request)
search.start { response, error in
guard let response = response else {
print("There was an error searching for: \(request.naturalLanguageQuery) error: \(error)")
return
}
print("There are \(response.mapItems.count)")
for item in response.mapItems {
// You may be able to match the address to what the geoCode gives you
// or present the user with a list of options
print("\(item.name), \(item.placemark)")
}
}
When I was testing this, the addresses didn't always match up, even when zoomed in - so that geoCoder might give me 1-3 Some Street while the MKLocalSearchRequest returned a restaurant at 3 Some Street
Related
I have 2 locations in the map. First one is users current location with blue dot placemark and the second one is specified restaurant location near of the users current location with red dot placemark. I want to get directions from user's current location to restaurant location. I am getting directions when I define latitude and longitude values like latitude: 38.00001 and longitude: 27.08980.
Also I am getting each restaurants location from api to red dot placemark but do not get directions from user current location to restaurant location.
I am getting restaurant location with these codes:
var viewModel: DetailsViewModel? {
didSet {
updateView()
}
willSet {
updateView()
}
}
func updateView() {
if let viewModel = viewModel {
detailsFoodView?.priceLabel?.text = viewModel.price
detailsFoodView?.hoursLabel?.text = viewModel.isOpen
detailsFoodView?.locationLabel?.text = viewModel.phoneNumber
detailsFoodView?.ratingsLabel?.text = viewModel.rating
detailsFoodView?.collectionView?.reloadData()
centerMap(for: viewModel.coordinate)
print("detail restaurant destination : \(viewModel.coordinate)")
title = viewModel.name
print("title: \(title)")
}
}
func centerMap(for coordinate: CLLocationCoordinate2D) {
let region = MKCoordinateRegion(center: coordinate, latitudinalMeters: 100, longitudinalMeters: 100)
let annotation = MKPointAnnotation()
annotation.coordinate = coordinate
detailsFoodView?.mapView?.addAnnotation(annotation)
detailsFoodView?.mapView?.setRegion(region, animated: true)
}
}
And in viewDidLoad() getting directions like:
let sourceCoordinates = locationManager.location?.coordinate
Problem is here
I do not get each restaurant's latitude and longitude values to destCoordinates. Normally, I get with viewModel.coordinate.
But if I use
let destCoordinates = CLLocationCoordinate2D(latitude: viewModel?.coordinate.latitude, longitude: viewModel?.coordinate.longitude)
Instead of:
let destCoordinates = CLLocationCoordinate2D(latitude: 38.420183, longitude: 27.205559)
I am getting latitude and longitude values nil or zero.
let sourcePlaceMark = MKPlacemark(coordinate: sourceCoordinates!)
let destPlaceMark = MKPlacemark(coordinate: destCoordinates)
let sourceItem = MKMapItem(placemark: sourcePlaceMark)
let destItem = MKMapItem(placemark: destPlaceMark)
let directionRequest = MKDirections.Request()
directionRequest.source = sourceItem
directionRequest.destination = destItem
directionRequest.transportType = .automobile
let directions = MKDirections(request: directionRequest)
directions.calculate(completionHandler: {response, error in
guard let response = response else {
if error != nil {
print("Something went wrong!")
}
return
}
let route = response.routes[0]
self.detailsFoodView?.mapView?.addOverlay(route.polyline, level: .aboveRoads)
let rect = route.polyline.boundingMapRect
self.detailsFoodView?.mapView?.setRegion(MKCoordinateRegion(rect), animated: true)
Expected result is current user place as blue dot placemark to red dot placemark with red line as you can see in the screen shot.
Image:
Idea :
App lets drivers see the closest shop/restaurants to customers.
What I have :
Coordinates saved as strings
let clientLat = "24.449384"
let clientLng = "56.343243"
a function to find all the shops in my local area
I tried to save all the coordinates of a shop in my local area and I succeeded:
var coordinates: [CLLocationCoordinate2D] = [CLLocationCoordinate2D]()
func performSearch() {
coordinates.removeAll()
let request = MKLocalSearchRequest()
request.naturalLanguageQuery = "starbucks"
request.region = mapView.region
let search = MKLocalSearch(request: request)
search.start(completionHandler: {(response, error) in
if error != nil {
print("Error occured in search: \(error!.localizedDescription)")
} else if response!.mapItems.count == 0 {
print("No matches found")
} else {
print("Matches found")
for item in response!.mapItems {
self.coordinates.append(item.placemark.coordinate)
// need to sort coordinates
// need to find the closest
let annotation = MKPointAnnotation()
annotation.coordinate = item.placemark.coordinate
annotation.title = item.name
self.mapView.addAnnotation(annotation)
}
}
})
}
What I need:
I wish to loop through the coordinates and find the closest shop (kilometers) to the lat and long strings then put a pin on it.
UPDATE
func performSearch() {
coordinates.removeAll()
let request = MKLocalSearchRequest()
request.naturalLanguageQuery = "starbucks"
request.region = mapView.region
let search = MKLocalSearch(request: request)
search.start(completionHandler: {(response, error) in
if error != nil {
print("Error occured in search: \(error!.localizedDescription)")
} else if response!.mapItems.count == 0 {
print("No matches found")
} else {
print("Matches found")
for item in response!.mapItems {
self.coordinates.append(item.placemark.coordinate)
let pointToCompare = CLLocation(latitude: 24.741721, longitude: 46.891440)
let storedCorrdinates = self.coordinates.map({CLLocation(latitude: $0.latitude, longitude: $0.longitude)}).sorted(by: {
$0.distance(from: pointToCompare) < $1.distance(from: pointToCompare)
})
self.coordinate = storedCorrdinates
}
let annotation = MKPointAnnotation()
annotation.coordinate = self.coordinate[0].coordinate
self.mapView.addAnnotation(annotation)
}
})
}
Thank you #brimstone
You can compare distances between coordinates by converting them to CLLocation types and then using the distance(from:) method. For example, take your coordinates array and map it to CLLocation, then sort that based on the distance from the point you are comparing them to.
let coordinates = [CLLocationCoordinate2D]()
let pointToCompare = CLLocation(latitude: <#yourLat#>, longitude: <#yourLong#>)
let sortedCoordinates = coordinates.map({CLLocation(latitude: $0.latitude, longitude: $0.longitude)}).sorted(by: {
$0.distance(from: pointToCompare) < $1.distance(from: pointToCompare)
})
Then, to set your annotation's coordinate to the nearest coordinate, just subscript the sortedCoordinates array.
annotation.coordinate = sortedCoordinates[0].coordinate
I would like to share my solution :)
1) In my case, I upload data from the API, so I need to create a model.
import MapKit
struct StoresMap: Codable {
let id: Int?
let title: String?
let latitude: Double?
let longitude: Double?
let schedule: String?
let phone: String?
let ukmStoreId: Int?
var distanceToUser: CLLocationDistance?
}
The last variable is not from API, but from myself to define distance for each store.
2) In ViewController I define:
func fetchStoresList() {
NetworkManager.downloadStoresListForMap(firstPartURL: backendURL) { (storesList) in
self.shopList = storesList
let initialLocation = self.locationManager.location!
for i in 0..<self.shopList.count {
self.shopList[i].distanceToUser = initialLocation.distance(from: CLLocation(latitude: self.shopList[i].latitude!, longitude: self.shopList[i].longitude!))
}
self.shopList.sort(by: { $0.distanceToUser! < $1.distanceToUser!})
print("Closest shop - ", self.shopList[0])
}
}
3) Don't forget to call the function in viewDidLoad() and import MapView framework :)
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.
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)
}
I have application with map where you can make an annotation by dropping a pin. How can I save the annotation, so you can see it when the application is closed and re-opened?
My code's for annotation
func addAnnotation(gesture: UIGestureRecognizer) {
if gesture.state == UIGestureRecognizerState.Began {
var touch = gesture.locationInView(self.Mapa)
var coordinate = Mapa.convertPoint(touch, toCoordinateFromView: self.Mapa)
var location = CLLocationCoordinate2D(latitude: coordinate.latitude, longitude: coordinate.longitude)
var loc = CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)
CLGeocoder().reverseGeocodeLocation(loc, completionHandler: { (placemarks, error) -> Void in
if error == nil {
let placemark = CLPlacemark(placemark: placemarks[0] as! CLPlacemark)
self.cislo = placemark.subThoroughfare != nil ? placemark.subThoroughfare : ""
self.adresa = placemark.thoroughfare != nil ? placemark.thoroughfare : ""
self.mesto = placemark.subAdministrativeArea != nil ? placemark.subAdministrativeArea : ""
self.krajina = placemark.administrativeArea != nil ? placemark.administrativeArea : ""
let annotation = MKPointAnnotation()
annotation.coordinate = placemark.location.coordinate
annotation.title = self.adresa! + " " + self.cislo!
self.Mapa.addAnnotation(annotation)
println("Špendlík pridaný!")
}
})
}
}
In case you want to see whole code
http://pastebin.com/d89kTrL7
i would save the data into userdefaults as
func addAnnotation(gesture: UIGestureRecognizer) {
if gesture.state == UIGestureRecognizerState.Began {
var touch = gesture.locationInView(self.Mapa)
var coordinate = Mapa.convertPoint(touch, toCoordinateFromView: self.Mapa)
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setDouble(coordinate.longitude, forKey: "longitudeNameKey")
defaults.setDouble(coordinate.latitude, forKey: "latitudeNameKey")
defaults.synchronize()
var location = CLLocationCoordinate2D(latitude: coordinate.latitude, longitude: coordinate.longitude)
var loc = CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)
CLGeocoder().reverseGeocodeLocation(loc, completionHandler: { (placemarks, error) -> Void in
if error == nil {
let placemark = CLPlacemark(placemark: placemarks[0] as! CLPlacemark)
self.cislo = placemark.subThoroughfare != nil ? placemark.subThoroughfare : ""
self.adresa = placemark.thoroughfare != nil ? placemark.thoroughfare : ""
self.mesto = placemark.subAdministrativeArea != nil ? placemark.subAdministrativeArea : ""
self.krajina = placemark.administrativeArea != nil ? placemark.administrativeArea : ""
let annotation = MKPointAnnotation()
annotation.coordinate = placemark.location.coordinate
annotation.title = self.adresa! + " " + self.cislo!
self.Mapa.addAnnotation(annotation)
println("Špendlík pridaný!")
}
})
}
}
You can save info to NSUserDefaults when the annotations are created.And somewhere in viewDidLoad method you just get all the info from user defaults and then display the annotations.
override func viewDidLoad() {
super.viewDidLoad()
loadAnnotationFromUserDefaults()
}
use loadAnnotationFromUserDefaults method to deserializes the list of coordinates previously saved to NSUserDefaults. Through this method you also load the coordinates as annotations on the map view.
func loadAnnotationFromUserDefaults(){
let defaults = NSUserDefaults.standardUserDefaults()
let long= defaults.doubleForKey("longitudeNameKey")
let lat = defaults.doubleForKey("latitudeNameKey")
println("\(long)")
println("\(lat)")
//You got the coordinates that you lost after terminating now load the coordinates as annotation to mapview
}
You should set new coordinates and terminate application ..notice the coordinates..now again reopen your application ..now you get again those see on the log
P.S code not tested and should change according to your application architecture...only take it as a reference.
Here is the demo project i set up for you
https://drive.google.com/open?id=0B6dTvD1JbkgBRnN2QllWWlJqd0E&authuser=0