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

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)
}
}

Related

How to Update Annotation Coordinates on MapView

I have a mapView and vehicle coordinates which changes in every 15 seconds so I want to update the coordinates. My current approach is to delete all annotations and adding new ones. However, I can't use animations in that approach, they are just spawning. When I looked google I've found out that people just changing the coordinates of annotations and it's happening. Not for me unfortunately.
Old version:
func updateVehicleLocations(){
let annotations = mapView.annotations.filter {
$0.title != "person"
}
mapView.removeAnnotations(annotations)
for vehicle in vehicles {
let pin = MKPointAnnotation()
pin.coordinate = CLLocationCoordinate2D(latitude: vehicle.latitude, longitude: vehicle.longitude)
pin.title = vehicle.vehicleID
mapView.addAnnotation(pin)
}
if isSetCoordinatesMoreThanOnce { return }
mapView.showAnnotations(mapView.annotations, animated: true)
isSetCoordinatesMoreThanOnce = true
}
My test:
func updateVehicleLocations(){
for annotation in busAnnotations {
UIView.animate(withDuration: 0.5) { [self] in
if let vehicle = self.vehicles.first(where: { $0.vehicleID == annotation.vehicleID }) {
annotation.coordinate = CLLocationCoordinate2D(latitude: vehicle.latitude, longitude: vehicle.longitude)
}
}
}
if isSetCoordinatesMoreThanOnce { return }
for vehicle in vehicles {
let pin = BusAnnotation(coordinate: CLLocationCoordinate2D(latitude: vehicle.latitude, longitude: vehicle.longitude), vehicleID: vehicle.vehicleID)
busAnnotations.append(pin)
}
mapView.addAnnotations(busAnnotations)
mapView.showAnnotations(mapView.annotations, animated: true)
isSetCoordinatesMoreThanOnce = true
}
That was exhausting, I still don't get the logic but here is how I fixed it;
I had to use a custom class(of MKAnnotation) to set coordinates otherwise the coordinate property is read-only. So, here is an example custom annotation class;
final class BusAnnotation: NSObject, MKAnnotation{
var vehicleID: String
var coordinate: CLLocationCoordinate2D
init(coordinate: CLLocationCoordinate2D, vehicleID:String) {
self.coordinate = coordinate
self.vehicleID = vehicleID
super.init()
}
}
however, it doesn't update coordinates, to fix it we need to add "dynamic" keyword to coordinate property everything works fine!
final class BusAnnotation: NSObject, MKAnnotation{
var vehicleID: String
dynamic var coordinate: CLLocationCoordinate2D
init(coordinate: CLLocationCoordinate2D, vehicleID:String) {
self.coordinate = coordinate
self.vehicleID = vehicleID
super.init()
}
}

How to remove "trace" of a mark in swift

I'm working on a tracker app and I managed to pull some data from the server and update other user's so called track. But I have this mark .person, which appears on every step. How Should I remove it?
Polyline is drawn like this:
private func updateOnlineTrackOnMap() {
let locations = self.track?.points?.map { $0.location } ?? []
guard locations.count >= 2 else { return }
mapView.drawPolyline(withLocations: locations)
mapView.setMark(.person, to: locations.last!.coordinate)
}
But mapView.removeMark(.person) doesn't work.
Ok, there were some custom methods. I've managed to do the trick like so:
if let oldAnnotation = self.drawer.oldAnnotation {
mapView.removeAnnotation(oldAnnotation)
}
let annotation = MKPointAnnotation()
annotation.coordinate = locations.last!.coordinate
annotation.title = "Speed"
let speed = locations.last!.speed * 3.6
annotation.subtitle = String(format: "%.2f km/h", speed)
mapView.addAnnotation(annotation)
mapView.selectAnnotation(annotation, animated: false)
oldAnnotation = annotation
mapView.drawPolyline(withLocations: locations)
There's just a global variable to store current annotation coordinate. And then in mapView :viewFor: I wrote something like this:
if annotation is MKPointAnnotation {
let annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: "Online")
annotationView.image = rotatedArrow
annotationView.canShowCallout = true
return annotationView
}

How to store drop pin destination coordinates swift

I'm trying to store the drop pin destination coordinates and pass them back through the delegate? I have the below drop pin function.
func dropPinFor(placemark: MKPlacemark) {
selectedItemPlacemark = placemark
for annotation in mapView.annotations {
if annotation.isKind(of: MKPointAnnotation.self) {
// mapView.removeAnnotation(annotation) // removing the pins from the map
}
}
let annotation = MKPointAnnotation()
annotation.coordinate = placemark.coordinate
mapView.addAnnotation(annotation)
let (destLat, destLong) = (placemark.coordinate.latitude, placemark.coordinate.longitude)
print("This is the pins destinations coord \(destLat, destLong)")
}
But when I try to print before sending data back through delegate it print's 0.0 lat 0.0 long
#IBAction func addBtnWasPressed(_ sender: Any) {
if delegate != nil {
if firstLineAddressTextField.text != "" && cityLineAddressTextField.text != "" && postcodeLineAddressTextField.text != "" {
//Create Model object DeliveryDestinations
let addressObj = DeliveryDestinations(NameOrBusiness: nameOrBusinessTextField.text, FirstLineAddress: firstLineAddressTextField.text, SecondLineAddress: countryLineAddressTextField.text, CityLineAddress: cityLineAddressTextField.text, PostCodeLineAddress: postcodeLineAddressTextField.text, DistanceToDestination: distance, Lat: destlat, Long: destlong)
print(distance)
print("This is the latitude to use with protocol \(destlat)")
print("This is the latitude to use with protocol \(destlong)")
//add that object to previous view with delegate
delegate?.userDidEnterData(addressObj: addressObj)
//Dismising VC
//navigationController?.popViewController(animated: true)
clearTextFields()
}
}
}
You are declaring the (destLat, destLong) inside the dropPinFor method, so your tuple is redeclared you need only assign the value in the dropPinFor
Declaration
var coordinate : (Double, Double) = (0,0)
Code
func dropPinFor(placemark: MKPlacemark) {
selectedItemPlacemark = placemark
for annotation in mapView.annotations {
if annotation.isKind(of: MKPointAnnotation.self) {
// mapView.removeAnnotation(annotation) // removing the pins from the map
}
}
let annotation = MKPointAnnotation()
annotation.coordinate = placemark.coordinate
mapView.addAnnotation(annotation)
self.coordinate = (placemark.coordinate.latitude, placemark.coordinate.longitude)
print("This is the pins destinations coord \(destLat, destLong)")
}

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

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)
}

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