How to pass CLLocation to different ViewController - ios

My app has two tabs. On the first tab a bunch of tab annotations are created based on an attraction. When you click on the annotation view, it goes to the 2nd tab which is a detail view controller of the attraction. I am wondering how I can pass the location of the attraction to the detailVC that way I can have the pin at the same address. Here is what I have so far, and there are some inconsistencies, as the address displayed on the detail vc is not equal to the address of the attraction.
func performSearch(input:String) {
attractionsMap.removeAnnotations(attractionsMap.annotations);
matchingItems.removeAll()
let request = MKLocalSearchRequest()
request.naturalLanguageQuery = input
println(input);
request.region = attractionsMap.region;
let search = MKLocalSearch(request: request)
search.startWithCompletionHandler({(response:
MKLocalSearchResponse!,
error: NSError!) in
if error != nil {
println("Error occured in search: \(error.localizedDescription)")
} else if response.mapItems.count == 0 {
println("No matches found")
} else {
println("Matches found")
for item in response.mapItems as! [MKMapItem] {
println("Name = \(item.name)")
println("Phone = \(item.phoneNumber)")
matchingItems.append(item as MKMapItem)
println("Matching items = \(matchingItems.count)")
var placemark = item.placemark;
var subThoroughfare:String = "";
var thoroughfare:String = "";
var locality:String = "";
var postalCode:String = "";
var administrativeArea:String = "";
var country:String = "";
var title = "";
var subtitle = "";
if (placemark.subThoroughfare != nil) {
subThoroughfare = placemark.subThoroughfare;
}
if(placemark.thoroughfare != nil) {
thoroughfare = placemark.thoroughfare;
}
if(placemark.locality != nil) {
locality = placemark.locality;
}
if(placemark.postalCode != nil) {
postalCode = placemark.postalCode;
}
if(placemark.administrativeArea != nil) {
administrativeArea = placemark.administrativeArea;
}
if(placemark.country != nil) {
country = placemark.country;
}
println("viewcontroller placmark data:");
println(locality);
println(postalCode);
println(administrativeArea);
println(country);
title = " \(subThoroughfare) \(thoroughfare) \n \(locality), \(administrativeArea) \n \(postalCode) \(country)";
subtitle = " \(subThoroughfare) \(thoroughfare)";
println(title);
var annotation = MKPointAnnotation()
annotation.coordinate = item.placemark.coordinate
annotation.title = item.name + " " + subtitle;
self.attractionsMap.addAnnotation(annotation)
}
}
})
}
This is the search and adding the pin annotations.
I then attempt to send the attractionLocation below
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
var attractionsDetailViewController:AttractionsDetailViewController = segue.destinationViewController as! AttractionsDetailViewController
attractionsDetailViewController.attractionLocation = indicatedMapItem;
}
And then my Detail ViewController to implement the given address:
func getInfo() {
var latitude = attractionLocation.latitude;
var longitude = attractionLocation.longitude;
var latDelta:CLLocationDegrees = 0.000001
var longDelta: CLLocationDegrees = 0.000001
var span: MKCoordinateSpan = MKCoordinateSpanMake(latDelta, longDelta);
var location = CLLocationCoordinate2DMake(latitude, longitude);
var realLocation = CLLocation(latitude: latitude, longitude: longitude);
CLGeocoder().reverseGeocodeLocation(realLocation, completionHandler: { (placemarks, error) -> Void in
var title = ""
var subtitle = ""
var locality = ""
if(error == nil) {
if let placemark = CLPlacemark(placemark: placemarks?[0] as! CLPlacemark) {
var subThoroughfare:String = "";
var thoroughfare:String = "";
var locality:String = "";
var postalCode:String = "";
var administrativeArea:String = "";
var country:String = "";
if (placemark.subThoroughfare != nil) {
subThoroughfare = placemark.subThoroughfare;
}
if(placemark.thoroughfare != nil) {
thoroughfare = placemark.thoroughfare;
}
if(placemark.locality != nil) {
locality = placemark.locality;
}
if(placemark.postalCode != nil) {
postalCode = placemark.postalCode;
}
if(placemark.administrativeArea != nil) {
administrativeArea = placemark.administrativeArea;
}
if(placemark.country != nil) {
country = placemark.country;
}
println("viewcontroller placmark data:");
println(locality);
println(postalCode);
println(administrativeArea);
println(country);
title = " \(subThoroughfare) \(thoroughfare) \n \(locality), \(administrativeArea) \n \(postalCode)\(country)";
subtitle = " \(subThoroughfare) \(thoroughfare)";
println(title);
self.addressLabel.text = title;
}
}
var overallLoc = CLLocationCoordinate2DMake(latitude, longitude);
var region:MKCoordinateRegion = MKCoordinateRegionMake(overallLoc, span);
var annotation = MKPointAnnotation();
annotation.coordinate = location;
annotation.title = subtitle;
self.detailMap.addAnnotation(annotation);
self.detailMap.setRegion(region, animated: true)
})
}

You can store a global variable, which is usually not recommended, or you can pass via segue, which is recommended. It's probably better to not reverse geocode again because reverse geocoding is not always the most accurate method of finding locations...

If you make your attractionLocation property in the detailsVC like this...
var attractionLocation: CLLocation?
var attractionAddress: String?
As far as I can tell, the getInfo function just needs to set the map's region and add the appropriate annotation.
Seeing as the attractionLocation property will be a CLLocation we can access it's coordinate property. And if you pass through the attractionAddress from the first screen then CLGeocoder doesn't need to be used.
func getInfo() {
// Create span for the map region
var latDelta:CLLocationDegrees = 0.000001
var longDelta: CLLocationDegrees = 0.000001
var span: MKCoordinateSpan = MKCoordinateSpanMake(latDelta, longDelta);
// Create region for map
var region:MKCoordinateRegion = MKCoordinateRegionMake(attractionLocation!.coordinate, span);
var annotation = MKPointAnnotation();
annotation.coordinate = attractionLocation!.coordinate;
annotation.title = attractionAddress!;
self.detailMap.addAnnotation(annotation);
self.detailMap.setRegion(region, animated: true)
}
In the past I've not found CLGeocoder to be that consistent when it comes to reverse geocoding. So at least this way you can be sure the address between the two screens will be consistent.

Related

Google map Place information by latitude and longitude in iOS Swift

I have a marker on map. When scroll map, then the marker also moves. I can find the marker coordinates, but how to find place information using that coordinate?
Place information of current location
func locate() {
placesClient.currentPlace(callback: { (placeLikelihoodList, error) -> Void in
if let error = error {
print("Pick Place error: \(error.localizedDescription)")
return
}
let placeInfo = getCurrentPlaceInformation()
self.placeNameLbl.text = placeInfo.name
self.placeAddressLbl.text = placeInfo.address
if let placeLikelihoodList = placeLikelihoodList {
let place = placeLikelihoodList.likelihoods.first?.place
if let place = place {
print("LOG: place name : \(place.name), place Address : \(place.formattedAddress)")
PLACE_NAME = place.name
PLACE_ADDRESS = place.formattedAddress ?? ""
let placeInfo = getCurrentPlaceInformation()
self.placeNameLbl.text = placeInfo.name
self.placeAddressLbl.text = placeInfo.address
}
}
})
}
How to find custom coordinates to find place information?
Apple reverse Geocode API
import CoreLocation
let geocoder = CLGeocoder()
geocoder.reverseGeocodeLocation(<#T##location: CLLocation##CLLocation#>, completionHandler: <#T##CLGeocodeCompletionHandler##CLGeocodeCompletionHandler##([CLPlacemark]?, Error?) -> Void#>)
Google reverse Geocode API
Add GoogleMaps to project (can use pods)
let geocoder = GMSGeocoder()
geocoder.reverseGeocodeCoordinate(position) { response, error in
//
if error != nil {
print("reverse geodcode fail: \(error!.localizedDescription)")
} else {
if let places = response?.results() {
if let place = places.first {
if let lines = place.lines {
print("GEOCODE: Formatted Address: \(lines)")
}
} else {
print("GEOCODE: nil first in places")
}
} else {
print("GEOCODE: nil in places")
}
}
}
func getAddrFrmLtLng(latitude:Any, longitude:Any){
let geoCoder = CLGeocoder()
let location = CLLocation(latitude: latitude as! CLLocationDegrees, longitude: longitude as! CLLocationDegrees)
geoCoder.reverseGeocodeLocation(location, completionHandler: { (placemarks, error) -> Void in
var placeMark: CLPlacemark!
placeMark = placemarks?[0]
self.displayLocationInfo(placemark: placeMark)
})
}
func displayLocationInfo(placemark: CLPlacemark?) -> String {
var locality = ""
var postalCode = ""
var administrativeArea = ""
var country = ""
var sublocality = ""
var throughfare = ""
var name = ""
if let containsPlacemark = placemark {
//stop updating location to save battery life
// locationManager.stopUpdatingLocation()
locality = (containsPlacemark.locality != nil) ? containsPlacemark.locality! : ""
postalCode = (containsPlacemark.postalCode != nil) ? containsPlacemark.postalCode! : ""
administrativeArea = (containsPlacemark.administrativeArea != nil) ? containsPlacemark.administrativeArea! : ""
country = (containsPlacemark.country != nil) ? containsPlacemark.country! : ""
sublocality = (containsPlacemark.subLocality != nil) ? containsPlacemark.subLocality! : ""
throughfare = (containsPlacemark.thoroughfare != nil) ? containsPlacemark.thoroughfare! : ""
}
var adr: String = ""
if throughfare != "" {
adr = throughfare + ", "
}
if sublocality != "" {
adr = adr + sublocality + ", "
}
if locality != "" {
adr = adr + locality + ", "
}
if administrativeArea != "" {
adr = adr + administrativeArea + ", "
}
if postalCode != "" {
adr = adr + postalCode + ", "
}
if country != "" {
adr = adr + country
}
print(adr)
return adr
}

Can I get a store name/restaurant name with mapkit?(swift)

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

Mapview - protection against coordinates found nil

I have mapview that downloads records off CloudKit. The coordinates of each record is based on forward geocoder, where users add the address (ex: New York, NY) and lats and lons are obtained
Current Model is as follow:
class Place: NSObject
{
var name: String
var address: String
var comment: String?
var photo: UIImage?
var rating: Int
var location: CLLocation?
var identifier: String
var record: CKRecord!
init(record: CKRecord)
{
self.record = record
self.name = record.valueForKey(placeName) as! String
self.address = record.valueForKey(placeAddress) as! String
self.comment = record.valueForKey(placeComment) as? String
if let photoAsset = record.valueForKey(placePhoto) as? CKAsset
{
self.photo = UIImage(data: NSData(contentsOfURL: photoAsset.fileURL)!)
}
self.rating = record.valueForKey(placeRating) as! Int
self.location = record.valueForKey(placeLocation) as? CLLocation
self.identifier = record.recordID.recordName
}
// MARK: Map Annotation
var coordinate: CLLocationCoordinate2D {
get {
return location!.coordinate
}
}
This is my method to place each pin on the mapview.
func placePins()
{
for place: Place in self.places
{
let location = CLLocationCoordinate2DMake(place.coordinate.latitude, place.coordinate.longitude)
let dropPin = CustomPointAnnotation(place: place)
dropPin.pinCustomImageName = "customPin"
dropPin.coordinate = location
dropPin.title = place.title
dropPin.subtitle = place.subtitle
dropPin.name = place.name
dropPin.image = place.photo
mapView.addAnnotation(dropPin)
}
}
How do i fix them to protect against any record that doesn't have coordinates since forward geocoder is not the most reliable way?
What about
for place: Place in self.places
{
if (place.location == nil) {
continue;
}
...
}
Not sure what is the issue there

Swift 2 MapKit Annotations - How to group annotations?

I have close to 8.000 annotations in my map and I'd like to group them depending of the zoom that the user do in the app.
All the latitudes and longitudes are already into the CoreData as Double.
Case I have two different annotation images in the same point, I'd like to show two groups, one with the cross and one with the heart.
When the user click over the annotation and if this annotation is a grouped annotation, I'd like to show to the user how many annotations has at this location, "forcing" the user to zoom in to see the annotations individually.
Bellow are the images of my app and the current annotations.
Thank you!
I already got it.
var zoomLevel = Double()
var iphoneScaleFactorLatitude = Double()
var iphoneScaleFactorLongitude = Double()
var CanUpdateMap: Bool = false
static func getLatitudeLongitudeLimitsFromMap(mapView: MKMapView) -> [String: Double] {
var coord = [String: Double]()
let MinLat: Double = mapView.region.center.latitude - (mapView.region.span.latitudeDelta / 2)
let MaxLat: Double = mapView.region.center.latitude + (mapView.region.span.latitudeDelta / 2)
let MinLon: Double = mapView.region.center.longitude - (mapView.region.span.longitudeDelta / 2)
let MaxLon: Double = mapView.region.center.longitude + (mapView.region.span.longitudeDelta / 2)
coord["MinLat"] = MinLat
coord["MaxLat"] = MaxLat
coord["MinLon"] = MinLon
coord["MaxLon"] = MaxLon
return coord
}
func LoadMap(mapView: MKMapView) {
// Get the limits after move or resize the map
let coord: [String: Double] = getLatitudeLongitudeLimitsFromMap(mapView)
let MinLat: Double = coord["MinLat"]! as Double
let MaxLat: Double = coord["MaxLat"]! as Double
let MinLon: Double = coord["MinLon"]! as Double
let MaxLon: Double = coord["MaxLon"]! as Double
var arrAnnotations = [MKAnnotation]()
let FilterMinLat = arrDicListPinsWithLatitudeLongitude.filter({
if let item = $0["Latitude"] as? Double {
return item > MinLat
} else {
return false
}
})
let FilterMaxLat = FilterMinLat.filter({
if let item = $0["Latitude"] as? Double {
return item < MaxLat
} else {
return false
}
})
let FilterMinLon = FilterMaxLat.filter({
if let item = $0["Longitude"] as? Double {
return item > MinLon
} else {
return false
}
})
let FilterMaxLon = FilterMinLon.filter({
if let item = $0["Longitude"] as? Double {
return item < MaxLon
} else {
return false
}
})
for Item in FilterMaxLon {
let dic:[String:AnyObject] = Item
var Name = String()
var Address = String()
var IconPNG = String()
if let Latitude = dic["Latitude"] as? Double {
if let Longitude = dic["Longitude"] as? Double {
if let item = dic["Name"] {
Name = item as! String
}
if let item = dic["Address"] {
Address = item as! String
}
if let item = dic["TypeID"] as? Int {
if item == 11 {
IconPNG = "icon-cross.png"
} else {
IconPNG = "icon-heart.png"
}
}
arrAnnotations.append(CreateAnnotation(Address, Title: Name, Latitude: Latitude, Longitude: Longitude, IconPNG: IconPNG))
}
}
}
}
// Show in the map only the annotations from that specific region
iphoneScaleFactorLatitude = mapView.region.center.latitude
iphoneScaleFactorLongitude = mapView.region.center.longitude
if zoomLevel != mapView.region.span.longitudeDelta {
filterAnnotations(arrAnnotations)
zoomLevel = mapView.region.span.longitudeDelta
CanUpdateMap = true
}
}
func filterAnnotations(arrAnnotations: [MKAnnotation]) {
let latDelta: Double = 0.04 / iphoneScaleFactorLatitude
let lonDelta: Double = 0.04 / iphoneScaleFactorLongitude
var shopsToShow = [AnyObject]()
var arrAnnotationsNew = [MKAnnotation]()
for var i = 0; i < arrAnnotations.count; i++ {
let checkingLocation: MKAnnotation = arrAnnotations[i]
let latitude: Double = checkingLocation.coordinate.latitude
let longitude: Double = checkingLocation.coordinate.longitude
var found: Bool = false
for tempPlacemark: MKAnnotation in shopsToShow as! [MKAnnotation] {
if fabs(tempPlacemark.coordinate.latitude - latitude) < fabs(latDelta) && fabs(tempPlacemark.coordinate.longitude - longitude) < fabs(lonDelta) {
found = true
}
}
if !found {
shopsToShow.append(checkingLocation)
arrAnnotationsNew.append(checkingLocation)
}
}
// Clean the map
for item: MKAnnotation in self.mapRedes.annotations {
myMap.removeAnnotation(item)
}
// Add new annotations to the map
for item: MKAnnotation in arrAnnotationsNew {
myMap.addAnnotation(item)
}
}
func mapView(mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
// This validation should be added, because it will find all the annotations before the map resize
if CanUpdateMap == true {
LoadMap(mapView)
}
}
Just a correction to my code:
//....
func LoadMap(mapView: MKMapView) {
//....
// Show in the map only the annotations from that specific region
iphoneScaleFactorLatitude = Double(mapView.bounds.size.width / 30) // 30 = width of the annotation
iphoneScaleFactorLongitude = Double(mapView.bounds.size.height / 30) // 30 = height of the annotation
//....
}
func filterAnnotations(mapView: MKMapView, arrAnnotations: [MKAnnotation]) {
let latDelta: Double = mapView.region.span.longitudeDelta / iphoneScaleFactorLatitude
let lonDelta: Double = mapView.region.span.longitudeDelta / iphoneScaleFactorLongitude
//....
}
//....

Showing drop pin after relaunch app?

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

Resources