Why does UILongPressGestureRecognizer return twice - ios

The following code seems to print the values twice even though I hold down for 2 seconds.
No matter what duration I change to it always seems to execute twice, does anyone know why this might be?
func action(gestureRecognizer:UIGestureRecognizer){
var touchPoint = gestureRecognizer.locationInView(self.myMap);
var newCo = myMap.convertPoint(touchPoint, toCoordinateFromView: self.myMap);
var annotation = MKPointAnnotation();
annotation.coordinate = newCo;
var loc = CLLocation(latitude: newCo.latitude, longitude: newCo.longitude);
CLGeocoder().reverseGeocodeLocation(loc, completionHandler: {(placemarks, error)->Void in
let pm:CLPlacemark = placemarks[0] as CLPlacemark;
var address = pm.locality + " ," + pm.postalCode + " ," + pm.administrativeArea + " ," + pm.country;
annotation.title = address;
self.myMap.addAnnotation(annotation);
println(address);
println("\(newCo.latitude)");
println("\(newCo.longitude)");
//places.append(["name:":address, "lat": "\(newCo.latitude)", "lon":"\(newCo.longitude)"]);
})
}

Check the state property of the UIGestureRecognizer, you're probably getting both begin and end.
enum UIGestureRecognizerState : Int {
case Possible
case Began
case Changed
case Ended
case Cancelled
case Failed
}

func action(gestureRecognizer:UIGestureRecognizer) {
print("Gesture Recognized")
if gestureRecognizer.state == UIGestureRecognizerState.Ended {
let touchPoint = gestureRecognizer.locationInView(self.map)
let newCoordinate:CLLocationCoordinate2D = self.map.convertPoint(touchPoint, toCoordinateFromView: self.map)
print(newCoordinate)
listNewCoordinates.append(newCoordinate)
let annotation = MKPointAnnotation()
annotation.coordinate.longitude = newCoordinate.longitude
annotation.coordinate.latitude = newCoordinate.latitude
self.map.addAnnotation(annotation)
}
}

Related

Remove travelled path from GMSPolyline on GMSMapView Swift iOS

I am using google distance api ["https://maps.googleapis.com/maps/api/directions/json?origin=" +start.latitude + "," + start.longitude +"&destination=" + end.latitude +"," + end.longitude + "&alternatives=false" +"&mode=driving&key=" + key;] to get route from start location to end location.
I am using the following code to draw route between my start and destination location
func drawPath()
{
if polylines != nil {
polylines?.map = nil
polylines = nil
}
if animationPolyline != nil {
self.animationIndex = 0
self.animationPath = GMSMutablePath()
self.animationPolyline.map = nil
if self.timer != nil {
self.timer.invalidate()
}
}
setupStartRideLocationMarkup(CLLocationCoordinate2D(latitude: (currentLocation?.coordinate.latitude)!, longitude: (currentLocation?.coordinate.longitude)!))
if currentLocation != nil && destinationLocation != nil {
let origin = "\((currentLocation?.coordinate.latitude)!),\((currentLocation?.coordinate.longitude)!)"
let destination = "\((destinationLocation?.latitude)!),\((destinationLocation?.longitude)!)"
let url = "https://maps.googleapis.com/maps/api/directions/json?origin=\(origin)&destination=\(destination)&mode=driving&key=MY_API_KEY"
Alamofire.request(url).responseJSON { response in
let json = JSON(data: response.data!)
self.jsonRoute = json
let routes = json["routes"].arrayValue
for route in routes
{
let routeOverviewPolyline = route["overview_polyline"].dictionary
let points = routeOverviewPolyline?["points"]?.stringValue
self.path = GMSPath.init(fromEncodedPath: points!)!
self.polylines = GMSPolyline.init(path: self.path)
self.polylines?.geodesic = true
self.polylines?.strokeWidth = 5
self.polylines?.strokeColor = UIColor.black
self.polylines?.map = self.mapView
}
self.shouldDrawPathToStartLocation()
self.shouldDrawPathToEndLocation()
if routes.count > 0 {
self.startAnimatingMap()
}
}
}
}
As you can see I am initialising path with encoded path from the api. Now I want to remove travelled GMSPolyline from the overall path How can I do that? My current intiuation is that it will be from didUpdateLocations Here's my code of didUpdateLocations method
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
currentLocation = locations.last!
let camera = GMSCameraPosition.camera(withLatitude: (currentLocation?.coordinate.latitude)!,
longitude: (currentLocation?.coordinate.longitude)!,
zoom: zoomLevel)
if (mapView?.isHidden)! {
mapView?.isHidden = false
mapView?.camera = camera
} else {
mapView?.animate(to: camera)
}
updatePolyLineIfRequired()
}
And in updatePolyLineIfRequired I want to remove travelled poly lines
func updatePolyLineIfRequired(){
if GMSGeometryIsLocationOnPath((currentLocation?.coordinate)!, path, true) {
if startPolyline != nil {
startPolyline?.map = nil
startPolyline = nil
}
}
}
I want to implement solution like Uber or Careem where travelled drawn GMSPolyline gets removed till user current location.
Thanks in Advance
P.S I am using Alamofire SwiftyJSON
There are two solutions for this:-
Calling Directions Api each time didUpdateLocations function is called.(Not efficient)
Removing the travelled coordinates from the GMSPath.
Calling Directions api will be not useful unless your request limit for Direction api is less.
For removing the travelled coordinates from the path:-
//Call this function in didUpdateLocations
func updateTravelledPath(currentLoc: CLLocationCoordinate2D){
var index = 0
for i in 0..<self.path.count(){
let pathLat = Double(self.path.coordinate(at: i).latitude).rounded(toPlaces: 3)
let pathLong = Double(self.path.coordinate(at: i).longitude).rounded(toPlaces: 3)
let currentLaenter code heret = Double(currentLoc.latitude).rounded(toPlaces: 3)
let currentLong = Double(currentLoc.longitude).rounded(toPlaces: 3)
if currentLat == pathLat && currentLong == pathLong{
index = Int(i)
break //Breaking the loop when the index found
}
}
//Creating new path from the current location to the destination
let newPath = GMSMutablePath()
for i in index..<Int(self.path.count()){
newPath.add(self.path.coordinate(at: UInt(i)))
}
self.path = newPath
self.polyline.map = nil
self.polyline = GMSPolyline(path: self.path)
self.polyline.strokeColor = UIColor.darkGray
self.polyline.strokeWidth = 2.0
self.polyline.map = self.mapView
}
The lat and longs are rounded of so that if the user is nearby the travelled location. Use the following extension to round of upto 3 decimal places or more according to requirement.
extension Double {
// Rounds the double to decimal places value
func rounded(toPlaces places:Int) -> Double {
let divisor = pow(10.0, Double(places))
return (self * divisor).rounded() / divisor
}
}

Return Type Nill

func getAddressFromLatLon() {
var locManager = CLLocationManager()
locManager.requestWhenInUseAuthorization()
var currentLocation = CLLocation()
if( CLLocationManager.authorizationStatus() == CLAuthorizationStatus.authorizedWhenInUse ||
CLLocationManager.authorizationStatus() == CLAuthorizationStatus.authorized){
currentLocation = locManager.location!
}
var center : CLLocationCoordinate2D = CLLocationCoordinate2D()
let lat: Double = Double(currentLocation.coordinate.latitude)
//21.228124
let lon: Double = Double(currentLocation.coordinate.longitude)
//72.833770
let ceo: CLGeocoder = CLGeocoder()
center.latitude = lat
center.longitude = lon
let geoCoder = CLGeocoder()
var placemark: AnyObject
var error: NSError
geoCoder.reverseGeocodeLocation(locManager.location!, completionHandler: { (placemark, error) -> Void in
if error != nil {
print("Error: \(error?.localizedDescription)")
return
}
if (placemark?.count)! > 0 {
let pm = (placemark?[0])! as CLPlacemark
//self.addressString = pm.locality! + pm.country!
let adress = pm.locality! + " " + pm.country!
print(adress)
} else {
print("Error with data")
}
})
}
Sorry about this very basic question. I am fairly new to swift and I am trying to reverse geocode a latitude and longitude. I am trying to return the adress from the reverse geocoding, but it seems to be returning nil. The function in question is getAddressFromLatLon() and I have tried adding a return type but it is returning nil. When I print in the function itself, the correct value is printed but for some reason I am having difficulty getting the adress to return so I can pass it to other classes/functions.
You need to hold the value which you get from reserver geocoder and just need to use it.
geoCoder.reverseGeocodeLocation(locManager.location!, completionHandler: { (placemark, error) -> Void in
if error != nil {
print("Error: \(error?.localizedDescription)")
return
}
if (placemark?.count)! > 0 {
let pm = (placemark?[0])! as CLPlacemark
//self.addressString = pm.locality! + pm.country!
let adress = pm.locality! + " " + pm.country!
// Store value into global object and you can use it...
// Another option, you can call one function and do necessary steps in it
mainLocality = pm.locality
mainCountry - pm.country
updateGeoLocation() // Call funcation
print(adress)
} else {
print("Error with data")
}
})
func updateGeoLocation(){
// You can use both object at here
// mainLocality
// mainCountry
// Do your stuff
}
You need to initilise CLLocation object in ViewDidLoad and get location coordinatites in it delegate method
Declate variable for current location
var currentLocation : CLLocation
In ViewDidLoad
var locManager = CLLocationManager()
locationManager.delegate = self;
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
// ask permission - NOT NECESSARY IF YOU ALREADY ADDED NSLocationAlwaysUsageDescription IT UP INFO.PLIST
locationManager.requestAlwaysAuthorization()
// when in use foreground
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
CLLocation's Delegate
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
// Get first location item returned from locations array
currentLocation = locations[0]
self.getAddressFromLatLon() // you can call here or whenever you want
}
Your method as below
func getAddressFromLatLon() {
var center : CLLocationCoordinate2D = CLLocationCoordinate2D()
let lat: Double = Double(currentLocation.coordinate.latitude)
//21.228124
let lon: Double = Double(currentLocation.coordinate.longitude)
//72.833770
let ceo: CLGeocoder = CLGeocoder()
center.latitude = lat
center.longitude = lon
let geoCoder = CLGeocoder()
var placemark: AnyObject
var error: NSError
geoCoder.reverseGeocodeLocation(locManager.location!, completionHandler: { (placemark, error) -> Void in
if error != nil {
print("Error: \(error?.localizedDescription)")
return
}
if (placemark?.count)! > 0 {
let pm = (placemark?[0])! as CLPlacemark
//self.addressString = pm.locality! + pm.country!
let adress = pm.locality! + " " + pm.country!
print(adress)
} else {
print("Error with data")
}
})
}

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

Is it possible to have custom reverse geocoding?

Currently, I'm utilizing reverse geocoding to simply convert a longitude and latitude to a locality and sub locality.
Is it possible for me to override this and essentially have it return custom strings at my discretion given that I provide it with the coordinates? Any thoughts?
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
locationManager.stopUpdatingLocation()
if(locations.count > 0){
let location = locations[0] as! CLLocation
// println(location.coordinate)
if let currentLocatino = currLocation {
if CLLocation(latitude: currentLocatino.latitude, longitude: currentLocatino.longitude).distanceFromLocation(location) > 500 {
currLocation = location.coordinate
self.skip = 0
self.loadObjects()
}
}
else {
currLocation = location.coordinate
self.skip = 0
self.loadObjects()
}
CLGeocoder().reverseGeocodeLocation(CLLocation(latitude: currLocation!.latitude, longitude: currLocation!.longitude), completionHandler: {(placemarks, error) -> Void in
if error != nil {
println("Reverse geocoder failed with error" + error.localizedDescription)
return
}
if placemarks.count > 0 {
let date = NSDate()
let formatter = NSDateFormatter()
formatter.dateStyle = .MediumStyle
formatter.stringFromDate(date)
let pm = placemarks[0] as! CLPlacemark
var testifempty = "\(pm.subLocality)"
if testifempty == "nil"
{
self.locationManager.startUpdatingLocation()
if let lbutton = self.lbutton{
lbutton.text = "Hello " + "\(pm.locality)" //+ "\n" + formatter.stringFromDate(date)
}
}
else
{
self.locationManager.startUpdatingLocation()
if let lbutton = self.lbutton {
lbutton.text = "Hello " + "\(pm.subLocality)\n" // + formatter.stringFromDate(date)
}
}
}
else {
println("Problem with the data received from geocoder")
}
})
} else {
println("Cannot fetch your location")
}
}
If you know what the names are, you might try a switch that swapped out matching strings.
var locale_string
switch locale_string {
case “name1”:
let displayed_locale_name = “changed_name1”
case “name2”:
let displayed_locale_name = “changed_name2”
...
}
Then, the default case where you haven't identified the locale could accept the string as it is.
I got the idea from the Swift2 Programming Language Guide, page 17.
Reverse geocoding is basically a spatial query. I would suggest to define a map with spatial boundaries, assign each boundary an attribute for both locality and sub-locality.
With that map, you can run your set of coordinates against it and assign them the same attributes as the coordinate fits in one of the spatial boundaries.

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