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()
}
}
Related
I am using GoogleMaps to show the location marker on screens after fetching the location from Firestore database but the problem is I have three functions.
First function is showing all the list of users on the google maps, I called it in viewDidLoad() method.
func showListOfAllUsers() {
for document in snapshot!.documents {
print(document.data())
let marker = GMSMarker()
self.location.append(Location(trackingData: document.data()))
print(self.location)
guard let latitude = document.data()["Latitude"] as? Double else { return }
guard let longitude = document.data()["longitude"] as? Double else { return }
marker.position = CLLocationCoordinate2D(latitude: latitude as! CLLocationDegrees , longitude: longitude as! CLLocationDegrees)
marker.map = self.mapView
marker.userData = self.location
marker.icon = UIImage(named: "marker")
bounds = bounds.includingCoordinate(marker.position)
print("Data stored in marker \(marker.userData!)")
}
}
Now I presented a list of users in which I am passing the selected user co-ordinates to show the markers on the GoogleMaps.
func getAllLocationOfSelectedUserFromFirestore() {
for document in snapshot!.documents {
print(document.data())
let marker = GMSMarker()
self.location.append(Location(trackingData: document.data()))
print(self.location)
guard let latitude = document.data()["Latitude"] as? Double else { return }
guard let longitude = document.data()["longitude"] as? Double else { return }
marker.position = CLLocationCoordinate2D(latitude: latitude as! CLLocationDegrees , longitude: longitude as! CLLocationDegrees)
marker.map = self.mapView
marker.userData = self.location
bounds = bounds.includingCoordinate(marker.position)
print("Data stored in marker \(marker.userData!)")
}
}
I used delegate method to pass the selected user information.
extension MapViewController: ShowTrackingSalesMenListVCDelegate {
func didSelectedFilters(_ sender: ShowTrackingSalesMenListViewController, with userID: String) {
self.selectedUserID = userID
self.userLogButton.isHidden = false
print("The selected UserID is \(selectedUserID)")
self.getAllLocationOfSelectedUserFromFirestore() // called here the second function
}
Here is GMSMapViewDelegate function in which I am passing the user informations in userData.
func mapView(_ mapView: GMSMapView, didTap marker: GMSMarker) -> Bool {
print("didTap marker")
self.view.endEditing(true)
self.mapView.endEditing(true)
if let _ = self.activeMarker {
self.infoWindowView.removeFromSuperview()
self.activeMarker = nil
}
self.infoWindowView = MarkerInfoView()
let point = mapView.projection.point(for: marker.position)
self.infoWindowView.frame = CGRect(x: (point.x-(self.infoWindowView.width/2.0)), y: (point.y-(self.infoWindowView.height+25.0)), width: self.infoWindowView.width, height: self.infoWindowView.height)
self.activeMarker = marker
for mark in location {
self.infoWindowView.storeNameLabel?.text = mark.name
}
print(self.infoWindowView.storeNameLabel?.text as Any)
if let data = marker.userData as? [String:Any] {
print(data)
self.storeMapData = data
print(self.storeMapData)
var name = "N/A"
if let obj = data["name"] as? String {
name = obj
}
} else {
}
infoWindowView.delegate = self
self.mapView.addSubview(self.infoWindowView)
return true
}
It is showing the marker of the selected user on GoogleMaps. Now the problem is GMSMapViewDelegate function is same for both the above functions and it is showing the markers from both the functions on map. But I want to show only the selected user information on Maps. The red marker showing the selected user locations. How can I do this?
Just put a boolean flag and when you select the user set it to true and check it in the delegate and clear map overlay and put your marker only
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)
}
}
I have a situation where I have map annotations as [MKAnnotation] array in swift. Now I need to convert this into a set for some operation. How can I do this in swift? Basically I need to add only the non existent annotations on the map while updating the map view.
You can find out if an annotation exists on the map by using mapView.view(for:) as mentioned here:
if (self.mapView.view(for: annotation) != nil) {
print("pin already on mapview")
}
"Basically I need to add only the non existent annotations on the map while updating the map view."
We first need to define what makes two annotations equal (in your scenario). Once that is clear you an override the isEqual method. You can then add annotations to a Set.
Here is an example :
class MyAnnotation : NSObject,MKAnnotation{
var coordinate: CLLocationCoordinate2D
var title: String?
convenience init(coord : CLLocationCoordinate2D, title: String) {
self.init()
self.coordinate = coord
self.title = title
}
private override init() {
self.coordinate = CLLocationCoordinate2D(latitude: 0, longitude: 0)
}
override func isEqual(_ object: Any?) -> Bool {
if let annot = object as? MyAnnotation{
// Add your defintion of equality here. i.e what determines if two Annotations are equal.
return annot.coordinate.latitude == coordinate.latitude && annot.coordinate.longitude == coordinate.longitude && annot.title == title
}
return false
}
}
In the code above two instances of MyAnnotation are considered equal when you they have the same coordinates and the same title.
let ann1 = MyAnnotation(coord: CLLocationCoordinate2D(latitude: 20.0, longitude: 30.0), title: "Annot A")
let ann2 = MyAnnotation(coord: CLLocationCoordinate2D(latitude: 0.0, longitude: 0.0), title: "Annot B")
let ann3 = MyAnnotation(coord: CLLocationCoordinate2D(latitude: 20.0, longitude: 30.0), title: "Annot A")
var annSet = Set<MyAnnotation>()
annSet.insert(ann1)
annSet.insert(ann2)
annSet.insert(ann3)
print(annSet.count) // Output : 2 (ann1 & ann3 are equal)
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)")
}
I have an object array from a PFQuery and then add MKPinAnnotations in my mapView with the following function:
func addAnnotation() {
for (var i = 0; i < self.objectArray.count; i++) {
var location = object.objectForKey("location") as PFGeoPoint
var annotation = MKPointAnnotation()
annotation.setCoordinate(CLLocationCoordinate2D(latitude: location.latitude, longitude: location.longitude))
annotation.title = object.objectForKey("title") as NSString
var priceFormatter = NSNumberFormatter()
self.mapView.addAnnotation(annotation)
}
}
I have want to be able to click on the pin (or the information tab that shows when the pin is tapped) and segue to a detail view controller and pass the information associated with that pin or that index place in the Array...
I only need to know how to get the index number of the tapped pin so I can perform the segue...I've been stuck in this piece of code for the past 3 days and gave up and decided to ask the masters :P
Hope you can help me out please.
Thanks
Create a custom class for your annotation where you can store additional information such as the array index:
class PinAnnotation : NSObject, MKAnnotation {
private var coord: CLLocationCoordinate2D = CLLocationCoordinate2D(latitude: 0, longitude: 0)
var coordinate: CLLocationCoordinate2D {
get {
return coord
}
}
var title: String = ""
var subtitle: String = ""
var index: Int = 0
func setCoordinate(newCoordinate: CLLocationCoordinate2D) {
self.coord = newCoordinate
}
}
later in your code you can set the array index:
func addAnnotation() {
for (var i = 0; i < self.objectArray.count; i++) {
var location = object.objectForKey("location") as PFGeoPoint
var annotation = PinAnnotation()
annotation.setCoordinate(CLLocationCoordinate2D(latitude: location.latitude, longitude: location.longitude))
annotation.title = object.objectForKey("title") as NSString
annotation.index = i
var priceFormatter = NSNumberFormatter()
self.mapView.addAnnotation(annotation)
}
}
You have access to all the properties when you are working with the annotation object