Adding a pin annotation to a map view on a long press in swift - ios

I'm trying to make an iPhone app which requires users to be able to long press on a place on a map view to drop a pin there. Does anybody know how this is done?
The behaviour is observable in apple maps when you long press on the screen. It will drop a pin and present an annotation saying "dropped pin"

add UILongPressGestureRecognizer to your MapView
var uilgr = UILongPressGestureRecognizer(target: self, action: "addAnnotation:")
uilgr.minimumPressDuration = 2.0
map.add (uilgr)
//IOS 9
map.addGestureRecognizer(uilgr)
Add annotation on Long press detect - func:
func addAnnotation(gestureRecognizer:UIGestureRecognizer){
if gestureRecognizer.state == UIGestureRecognizerState.Began {
var touchPoint = gestureRecognizer.locationInView(map)
var newCoordinates = map.convertPoint(touchPoint, toCoordinateFromView: map)
let annotation = MKPointAnnotation()
annotation.coordinate = newCoordinates
CLGeocoder().reverseGeocodeLocation(CLLocation(latitude: newCoordinates.latitude, longitude: newCoordinates.longitude), completionHandler: {(placemarks, error) -> Void in
if error != nil {
println("Reverse geocoder failed with error" + error.localizedDescription)
return
}
if placemarks.count > 0 {
let pm = placemarks[0] as! CLPlacemark
// not all places have thoroughfare & subThoroughfare so validate those values
annotation.title = pm.thoroughfare + ", " + pm.subThoroughfare
annotation.subtitle = pm.subLocality
self.map.addAnnotation(annotation)
println(pm)
}
else {
annotation.title = "Unknown Place"
self.map.addAnnotation(annotation)
println("Problem with the data received from geocoder")
}
places.append(["name":annotation.title,"latitude":"\(newCoordinates.latitude)","longitude":"\(newCoordinates.longitude)"])
})
}
}
or you can add annotation without any title:
func action(gestureRecognizer:UIGestureRecognizer){
var touchPoint = gestureRecognizer.locationInView(map)
var newCoordinates = map.convertPoint(touchPoint, toCoordinateFromView: map)
let annotation = MKPointAnnotation()
annotation.coordinate = newCoordinates
map.addAnnotation(annotation)
}

1) Instantiate a UILongPressGestureRecognizer and add it to the MKMapView.
2) When the selector gets called after the user has a long press, call the addAnnotation method in MKMapView with the appropriate title and coordinate.
3) Then make sure you conform to the MKMapViewDelegate and implement viewForAnnotation: which will be called right after you add the annotation and return a MKPinAnnotationView

First declare UIGestureRecognizer in viewDidLoad
let longGesture = UILongPressGestureRecognizer(target: self, action: #selector(addWaypoint(longGesture:)))
mapView.addGestureRecognizer(longGesture)
Second add the function for longPress
#objc func addWaypoint(longGesture: UIGestureRecognizer) {
let touchPoint = longGesture.location(in: mapView)
let wayCoords = mapView.convert(touchPoint, toCoordinateFrom: mapView)
let location = CLLocation(latitude: wayCoords.latitude, longitude: wayCoords.longitude)
myWaypoints.append(location)
let wayAnnotation = MKPointAnnotation()
wayAnnotation.coordinate = wayCoords
wayAnnotation.title = "waypoint"
myAnnotations.append(wayAnnotation)
}
I recommend creating the annotations in an array that will serve you later if you want to delete it, like this...
var myAnnotations = [MKPointAnnotation]()
If you have different annotations, you can delete only the annotations you want, for that when you add a new annotation add to the array. To delete only one group of annotations just do the following
for dots in myAnnotations{
mapView.removeAnnotation(dots)
}
To delete all annotations try
mapView.removeAnnotations(mapView.annotations)
Apologies for the translation....

Update Swift3
func action(gestureRecognizer:UIGestureRecognizer){
let touchPoint = gestureRecognizer.location(in: mapView)
let newCoordinates = mapView.convert(touchPoint, toCoordinateFrom: mapView)
let annotation = MKPointAnnotation()
annotation.coordinate = newCoordinates
mapView.addAnnotation(annotation)
}

Related

Is there a preset 'index' or something similar that allows me to refer to specific annotations that a user created? Swift 4

I have an app where the user can search for locations, tap on the row in the table view, and an annotation will be placed at the placemark. I have a button, "meetUpButton" that allows them to put multiple annotations, rather than what my code does by default which is remove the annotation if a new search result is clicked. Is there a way to refer to a specific annotation that my user created, even if they made multiple? For example, say I want to add the latitude of two annotations that my user created...
If I have
let annotation = MKPointAnnotation()
annotation.coordinate.latitude
Is there a way to refer to something like the first annotation with an index of 0 and the next one that my user chose with a 1 or another way? Here's some of my code which might make this clearer.
extension ViewController: handleMapSearch {
func dropPinZoomIn(placemark:MKPlacemark) {
resultSearchController?.isActive = false
// cache the pin
selectedPin = placemark
// clear existing pins
if meetUpOutlet.isEnabled == true {
mapView.removeAnnotations(mapView.annotations)
} else {
}
let annotation = MKPointAnnotation()
annotation.coordinate = placemark.coordinate
annotation.title = placemark.name
if let city = placemark.locality,
let state = placemark.administrativeArea {
annotation.subtitle = "(\(city)) (\(state))"
}
mapView.addAnnotation(annotation)
let span = MKCoordinateSpanMake(0.05, 0.05)
let region = MKCoordinateRegionMake(placemark.coordinate, span)
mapView.setRegion(region, animated: true)
}
}
You can try to use annotations property of MKMapView instance , to get all use
let anns = self.mapView.annotations
You can implement Custom annotation class.
e.g.
class MyAnnotation : NSObject, MKAnnotation {
var coordinate : CLLocationCoordinate2D
var title: String?
var subtitle: String?
var index: Int?
init(location coord:CLLocationCoordinate2D) {
self.coordinate = coord
super.init()
}
}
Usage:
let annotation = MyAnnotation(location: coordinate)
annotation.title = "title"
annotation.subtitle = "subtitle"
annotation.index = 1
Remove annotations (only index == 1)
for annotation in self.mapView.annotations {
guard let annotation = annotation as? MyAnnotation else {
continue
}
if annotation.index == 1 {
self.mapView.removeAnnotation(annotation)
}
}

Remove Polylines at swift

I'm drawing navigation road at Swift. I'm using current location to another location and made a draw. Afterward, I select another location and redraw it. But even if I write mapView.remove(rotapoly) in my code, it doesnt remove it. How can I solve this?
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
cizim = 1;
let capital = view.annotation as! Station
guard let locValue: CLLocationCoordinate2D = locationManager.location?.coordinate else { return }
let neresi = CLLocationCoordinate2D(latitude: capital.latitude, longitude: capital.longitude)
let nerdeyim = CLLocationCoordinate2D(latitude: locValue.latitude, longitude: locValue.longitude)
let request = MKDirectionsRequest()
request.source = MKMapItem(placemark: MKPlacemark(coordinate: nerdeyim, addressDictionary: nil))
request.destination = MKMapItem(placemark: MKPlacemark(coordinate: neresi, addressDictionary: nil))
request.requestsAlternateRoutes = true
request.transportType = .walking
let directions = MKDirections(request: request)
directions.calculate { [unowned self] response, error in
guard let unwrappedResponse = response else { return }
if (unwrappedResponse.routes.count > 0) {
self.showRoute(response!)
}
}
}
func showRoute(_ response: MKDirectionsResponse) {
mapView.remove(rotapoly)
for route in response.routes {
rotapoly = route.polyline
mapView.add(rotapoly, level: MKOverlayLevel.aboveRoads)
for step in route.steps {
print(step.instructions)
}
}
}
use map view method
self.mapview.removeOverlays(self.mapview.overlays)
this will remove all overlays you have added so you have to do whole process again its like reloading map view
Below is the approach to remove travelled polyline from google map iOS Swift:
var oldPolyLines = [GMSPolyline]() /* Global Array Variable of your Class */
Put below code where you are parsing routes and getting new polyline from direction API.
if self.oldPolyLines.count > 0 {
for polyline in self.oldPolyLines {
polyline.map = nil
}
}
self.oldPolyLines.append(yourNewPolyline)
yourNewPolyLine.map = self.mapView

Swift 3 - LongPress on GMSMarker and print its coordinates (Google Maps iOS SDK)

Using Google Maps iOS SDK, I want to be able to print the coordinates of a GMSMarker if long pressed on that particular marker.
I first get all of my coordinates from a dictionary and drop markers for all coordinates on the map:
func placeMapMarkers() {
for item in self.finalDictionary as [Dictionary<String, String>] {
let lat = item["lat"] as! String
let lon = item["lon"] as! String
let SpotLat = Double(lat)
let SpotLon = Double(lon)
let SpotLocation = CLLocationCoordinate2DMake(SpotLat!, SpotLon!)
DispatchQueue.main.async(execute: {
self.SpotMarker = GMSMarker(position: SpotLocation)
self.SpotMarker?.icon = self.imageWithImage(image: UIImage(named: "SpotIcon")!, scaledToSize: CGSize(width: 35.0, height: 35.0))
self.SpotMarker?.title = "Long press to navigate here"
self.SpotMarker?.map = self.mapView
})
longPress(mapView: self.mapView, didLongPressAtCoordinate: SpotLocation)
}
}
My longPress function call is in the above placeMapMarkers function itself, since I want to identify while placing markers itself if a particular marker has been long pressed (I could be wrong with my thinking here).
My longPress function is below.
//This is long Press function:-
func longPressView(mapView: GMSMapView!, didLongPressAtCoordinate coordinate: CLLocationCoordinate2D) {
//Here handle your long press on map marker like:-
print("long pressed at \(coordinate)")
}
The problem is that I am getting "long pressed at" all coordinates from the dictionary of coordinates.
I want to
place markers for all coordinates in a dictionary
long press on a particular marker
and print coordinates for only that particular marker which was long pressed.
How do I go about this? Had a look at the other solutions, wasn't able to work out much.
I looked through the GMSMapView API. There is a method called "didLongPressAtCoordinate" that passes in a CLLocationCoordinate2D, so I think you can use that to create a GMSMarker. See here
You have to implement the GMSMapViewDelegate, and you can call
func mapView(mapView: GMSMapView!, didLongPressAtCoordinate coordinate: CLLocationCoordinate2D) {
let marker = GMSMarker(position: coordinate)
marker.title = "Found You!"
marker.map = mapView
}
Hope this one helps :)
I have done this before. You basically have to convert the touch point on the map to a coordinate.
#IBOutlet weak var mapView: MKMapView!
override func viewDidLoad() {
let uilpgr = UILongPressGestureRecognizer(target: self, action: #selector(userPerformedLongPress(gesture:)))
uilpgr.minimumPressDuration = 2.0
}
func userPerformedLongPress(gesture: UIGestureRecognizer) {
let touchPoint = gesture.location(in: mapView)
let newCoordinate: CLLocationCoordinate2D = mapView.convert(touchPoint, toCoordinateFrom: mapView)
let annotation = MKPointAnnotation()
annotation.coordinate = newCoordinate
annotation.title = "Location Selected"
annotation.subtitle = "Coordinate: \(round(1000*newCoordinate.longitude)/1000), \(round(1000*newCoordinate.latitude)/1000)"
mapView.addAnnotation(annotation)
print("Gesture recognized")
}

MapKit- How to add location constraints for dropping a pin?

I have an application that allows users to drop a pin anywhere on their map (MapKit), and it currently identifies and displays their current location. I would like to set a constraint so that users are unable drop a pin any further than 10 miles from their current location. Is this doable? I am somewhat new to dev, so I am not sure where to begin doing this. Here's a bit of the code that deals with dropping pin:
#IBOutlet weak var mapView: MKMapView!
//addpin
#IBAction func addPin(sender: UILongPressGestureRecognizer) {
//locating where to drop the pin
let location = sender.locationInView(self.mapView)
let locCoord = self.mapView.convertPoint(location, toCoordinateFromView:
self.mapView)
let annotation = MKPointAnnotation()
annotation.coordinate = locCoord
annotation.title = "Test"
annotation.subtitle = "subtext"
//remove map point (use later)
self.mapView.removeAnnotations(mapView.annotations)
self.mapView.addAnnotation(annotation)
}
You can use distance(from: ) method.
See: https://developer.apple.com/documentation/corelocation/cllocation/1423689-distance
#IBAction func addPin(sender: UILongPressGestureRecognizer) {
//locating where to drop the pin
let location = sender.locationInView(self.mapView)
let locCoord = self.mapView.convertPoint(location, toCoordinateFromView:
self.mapView)
// Get distance between pressed location and user location
let pressedLocation = CLLocation(latitude: locCoord.latitude, longitude: locCoord.longitude)
// let distanceInMeters = self.mapView.userLocation.location?.distance(from: pressedLocation) // >= Swift 3
let distanceInMeters = self.mapView.userLocation.location?.distanceFromLocation(pressedLocation) // < Swift 3
// You get here distance in meter so 10 miles = 16090 meter
if let distanceInMeters = distanceInMeters, distanceInMeters > 16090 {
// out of 10 mile (don't drop pin)
return
}
let annotation = MKPointAnnotation()
annotation.coordinate = locCoord
annotation.title = "Test"
annotation.subtitle = "subtext"
//remove map point (use later)
self.mapView.removeAnnotations(mapView.annotations)
self.mapView.addAnnotation(annotation)
}
However, there are couple of things that you need to put in place first.
You need to ask the user for permission to use their location. That includes calling CLLocationManager's requestWhenInUseAuthorization and checking for the asynchronous result.
Adding NSLocationWhenInUseUsageDescription entry to your info (described in the link above) to tell the user what you want to do with the information

I can't seem to set the MKMapView annotation title after the first time it has been set

What happens is I type in an address in the search field and click go. The location is found and the map zooms in. I click on the pin image and the title bubble pops up and shows the title.
I created a property "pin" to be a reference to the title property. After dragging to a new location has completed I set the property title label using the reference to the new location. In NSLog the property is showing as changed. But when I tap drag to a new location and dragging has ended the same original value of the title property remains unchanged.
I've looked at tons of questions about similar things and nothing is working.
Here is my GO button method:
#IBAction func didTapGoButton(sender: UIButton) {
self.spinnerContainer.hidden = false
var geocoder = CLGeocoder()
geocoder.geocodeAddressString(searchField.text, {(placemarks: [AnyObject]!, error: NSError!) -> Void in
if let placemark = placemarks?[0] as? CLPlacemark {
var region = self.mapView.region as MKCoordinateRegion
region.center = placemark.location.coordinate
region.span.longitudeDelta = 0.0144927536
region.span.latitudeDelta = 0.0144927536
self.mapView.zoomEnabled = true
self.mapView.scrollEnabled = true
let pa = MKPointAnnotation()
pa.coordinate = placemark.location.coordinate
pa.title = "\(placemark.name), \(placemark.locality), \(placemark.country)"
self.spinnerContainer.hidden = true
self.mapView.addAnnotation(pa)
self.pin = pa
self.mapView.setRegion(region, animated: true)
self.annotationTitle = pa.title
self.searchField.text = ""
//self.mapView.selectAnnotation(pa, animated: true)
var newLocation = CLLocation(latitude: pa.coordinate.latitude, longitude: pa.coordinate.longitude)
var geocoder = CLGeocoder()
geocoder.reverseGeocodeLocation(newLocation, completionHandler: { (placemarks: [AnyObject]!, error: NSError!) -> Void in
if let placemark = placemarks?[0] as? CLPlacemark {
// let pa = MKPointAnnotation()
pa.coordinate = placemark.location.coordinate
pa.title = "\(placemark.name), \(placemark.locality), \(placemark.administrativeArea), \(placemark.country), \(placemark.postalCode)"
NSLog("\(pa.title)")
self.addressFromCoordinates.text = pa.title
self.noAddressLabel.hidden = true
}
})
}
})
}
My viewForAnimation:
func mapView(mapView: MKMapView!, viewForAnnotation annotation: MKAnnotation!) -> MKAnnotationView! {
if annotation.isKindOfClass(MKUserLocation) {
return nil
}
let reuseId = "pin"
var pin = mapView.dequeueReusableAnnotationViewWithIdentifier(reuseId)
if pin == nil {
NSLog("PIN NIL")
pin = MKAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
pin.image = UIImage(named: "pin")
pin.draggable = true
pin.canShowCallout = true
}
else
{
NSLog("PIN NOT NIL")
pin.annotation = annotation
}
return pin;
}
my didChangeDragState method:
func mapView(mapView: MKMapView!, annotationView view: MKAnnotationView!, didChangeDragState newState: MKAnnotationViewDragState, fromOldState oldState: MKAnnotationViewDragState) {
if newState == MKAnnotationViewDragState.Starting
{
view.dragState = MKAnnotationViewDragState.Dragging
}
else if newState == MKAnnotationViewDragState.Ending || newState == MKAnnotationViewDragState.Canceling
{
view.dragState = MKAnnotationViewDragState.None
var newLocation = CLLocation(latitude: self.pin.coordinate.latitude, longitude: self.pin.coordinate.longitude)
var geocoder = CLGeocoder()
geocoder.reverseGeocodeLocation(newLocation, completionHandler: { (placemarks: [AnyObject]!, error: NSError!) -> Void in
if let placemark = placemarks?[0] as? CLPlacemark {
let pa = MKPointAnnotation()
pa.coordinate = placemark.location.coordinate
pa.title = "\(placemark.name), \(placemark.locality), \(placemark.administrativeArea), \(placemark.country), \(placemark.postalCode)"
self.annotationTitle = pa.title
self.addressFromCoordinates.text = pa.title
self.editAddressButton.hidden = false
self.noAddressLabel.hidden = true
}
})
NSLog("\(self.pin.coordinate.latitude), \(self.pin.coordinate.longitude)")
}
}
Is there some sort of refresh that needs to be done to the map view after dragging?
Thanks for your time
In didChangeDragState, this code:
let pa = MKPointAnnotation()
pa.coordinate = placemark.location.coordinate
pa.title = "\(placemark.name), \(placemark.locality), \(placemark.administrativeArea), \(placemark.country), \(placemark.postalCode)"
self.annotationTitle = pa.title
is not updating the title of the annotation just dragged because:
The pa in pa.title = ... is referring to a new instance of MKPointAnnotation which is not connected in any way to the annotation just dragged. The let pa = MKPointAnnotation() line creates a new instance of an MKPointAnnotation. This new instance just exists locally in memory and is not even added to the map.
The update to self.annotationTitle has no effect on the title of the annotation instance that was just dragged because annotationTitle is just some separate string variable you've declared and the original MKPointAnnotation that was added to the map and just dragged has no knowledge or connection whatsoever with annotationTitle (the fact that annotationTitle was initially set equal to the annotation's title in the didTapGoButton does not somehow "link" the two strings together).
In didTapGoButton, you are saving a reference to the annotation object that is actually added (and then dragged) in the pin variable. This pin variable is the reference you can use to update the title of the annotation that was dragged (assuming you will only have one annotation at a time).
So in didChangeDragState, change the code shown above to:
self.pin.coordinate = placemark.location.coordinate
self.pin.title = "\(placemark.name), \(placemark.locality), \(placemark.administrativeArea), \(placemark.country), \(placemark.postalCode)"
self.annotationTitle = self.pin.title
However, for your requirements, note that it's not really necessary to keep your own reference to the annotation in the first place...
In didChangeDragState, you can get access to the annotation that was dragged directly from the view argument that is passed into the method. For example:
let ann = view.annotation as MKPointAnnotation
ann.coordinate = placemark.location.coordinate
ann.title = "\(placemark.name), \(placemark.locality), \(placemark.administrativeArea), \(placemark.country), \(placemark.postalCode)"
self.annotationTitle = ann.title
Side Note:
After dragging the annotation and reverse geocoding the new location, there is a good possibility that the geocoded coordinates will be different than where the user dragged the annotation to. This happens because the nearest geocoded address may be slightly further from where the annotation was dragged to. So what will happen is the annotation will move a bit after the user finishes dragging. If you'd rather keep the annotation exactly where the user dragged it, don't update the annotation's coordinate in the geocoder's completion block and only update the title. You may want to set the title to something like "near xyz" if the geocoded address is X meters or more away from the actual coordinates.

Resources