I've currently figured out that some annotations are getting automatically hidden by MKMapView when zooming out.
I would like to know how many are hidden by one annotation to display this number when the user clicks on the annotation that regroups hidden ones, but I have no ideas how to do this.
Thank you in advance.
This code did everything for me :
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: MKMapViewDefaultAnnotationViewReuseIdentifier, for: annotation)
annotationView.clusteringIdentifier = "identifier"
return annotationView
}
from : Why clusterAnnotationForMemberAnnotations in MKMapView is not called?
Thank you for the hints #the4Man
Problem remaining : if Idon't want the current user location you have to set mapView.showsCurrentLocation = false
then I can't display user location ... I'm quite stuck there
EDIT : Solved it by returning nil when annotation.title = "My Location"
Hope this can help
You should check whether the annotation is an MKClusterAnnotation, which has a memberAnnotations property - an array of MKAnnotations. You should retrieve the count property of the array.
if let clusterAnnotation = annotation as? MKClusterAnnotation {
print(clusterAnnotation.memberAnnotations.count)
}
Related
I'm trying to call a function when annotations of the same location are selected. All I'm aiming for is to call a function and show a view controller with the two restaurants but for some reason, the function below isn't being called when I selected the marker in the image. (It works fine for single annotations). Thanks for any help.
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
guard let anno = view.annotation as? MyAnno else { return }
print(anno.post)
}
The problem lies in your guard statement. As I could find out in a short research, iOS uses a MKClusterAnnotation type for annotations that are equally located. Thus, the view variable does not have a .annotation set that confirms to your custom subclass.
In this case you need to try to cast it to a MKClusterAnnotation object, which in return has a property .memberAnnotations of type [MKAnnotation], which is what you want.
I think your code should look something like this (haven't tested it though):
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
if let anno = view.annotation as? MyAnno {
print("Single Annotation selected:")
print(anno.post)
// do anything
return
}
if let anno = view.annotation as? MKClusterAnnotations {
let selection: [MKAnnotation] = anno.memberAnnotations
print("\(selection.count) Annotations selected:")
// do something with your annotation group
// you should now also be able to cast to your MyAnno class again
for item in selection {
if let myAnno = item as? MyAnno {
print(myAnno.post)
}
}
}
}
For further information also take a look at Decluttering a Map with MapKit Annotation Clustering.
I am trying to make an Uber clone in which there are a lot of annotation for passengers and driver. I want to only load and show the annotation which belong to the map region currently displayed, so that all the annotations are not added to the mapview and does not consume much memory.
I have two types of annotations: driver and passenger. I have different images for their annotation.
Here is how I am trying to do it:
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if let annotation = annotation as? DriverAnnotation {
guard let dequeuedDriverAnnotation = mapView.dequeueReusableAnnotationView(withIdentifier: "driver") else {
let driverAnnotation = MKAnnotationView(annotation: annotation, reuseIdentifier: "driver")
driverAnnotation.image = UIImage(named: "driverAnnotation")
return driverAnnotation
}
dequeuedDriverAnnotation.annotation = annotation
return dequeuedDriverAnnotation
} else if let annotation = annotation as? PassengerAnnotation {
guard let dequeuedPassengerAnnotation = mapView.dequeueReusableAnnotationView(withIdentifier: "passenger") else {
let passengerAnnotation = MKAnnotationView(annotation: annotation, reuseIdentifier: "passenger")
passengerAnnotation.image = UIImage(named: "currentLocationAnnotation")
return passengerAnnotation
}
dequeuedPassengerAnnotation.annotation = annotation
return dequeuedPassengerAnnotation
}
return nil
}
}
How about defining function to check if annotation belong to the map region currently displayed, and try returning nil if it is out of region.
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if !visibleMapRegion(annotation) {
return nil
}
...
To define this function see also:
How to check if MKCoordinateRegion contains CLLocationCoordinate2D without using MKMapView?
How do I determine if the current user location is inside of my MKCoordinateRegion?
Is there any way that annotations might show other images then it should supose ? Is it a way to fix that ?
I've got a werid problem. After my clusters going to evolve to the single annotations then after updating ( zoom out - zoom in ) they images going to be diferent then it should be. After debuging it says that the color of the pin view image is the one which I suposed. Where I could hook up yet to see the exact data ? Maybe something after viewFor which get invoked.
Here is a logic how am I doing this.
The dictionary to knows witch model is on the top of the added annotations.
var dicAnnotations = [FBAnnotation : MyModel]()
Through the loop of [MyModel] i am appending data to the array of the [FBAnnotation] which is needed for saving them via FBClusteringManager. Then in the same time while the new annotation is added to the [FBAnnotation] I am adding
self.dicAnnotations[fbAnnotation] = objModel
to know which annotation have which object.
So as we know how I am soring the data of annotations, how Am I indexing them, lets have a look on the delegate of the MKMapView.
func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
DispatchQueue.global(qos: .userInitiated).async {
let mapBoundsWidth = Double(self.mapView.bounds.size.width)
let mapRectWidth = self.mapView.visibleMapRect.size.width
let scale = mapBoundsWidth / mapRectWidth
let annotationArray = self.clusterManager.clusteredAnnotations(withinMapRect: self.mapView.visibleMapRect, zoomScale:scale)
for ann in annotationArray {
if ann is FBAnnotation {
let a = ann as! FBAnnotation
print("Here u are , my color is : \(self.dicAnnotations[a]!.lineColor)")
}
}
DispatchQueue.main.async {
self.clusterManager.display(annotations: annotationArray, onMapView: self.mapView)
}
}
}
The for loop is for checking if the annotation is the one which should be - and it is!
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
var reuseId = ""
if annotation is FBAnnotationCluster {
reuseId = "Cluster"
var clusterView = mapView.dequeueReusableAnnotationView(withIdentifier: reuseId)
if clusterView == nil {
clusterView = FBAnnotationClusterView(annotation: annotation, reuseIdentifier: reuseId, configuration: FBAnnotationClusterViewConfiguration.default())
} else {
clusterView?.annotation = annotation
}
return clusterView
}
else {
let ann = annotation as! FBAnnotation
var pinView = mapView.dequeueReusableAnnotationView(withIdentifier: "Pin")
pinView = MKAnnotationView(annotation: ann, reuseIdentifier: "Pin")
var modelType = dicAnnotations[ann]?.modelType
modelType = modelType?.modelTypeToString(modelType: modelType!)
let modelColor = dicAnnotations[ann]?.lineColor
let modelDirection = dicAnnotations[ann]?.withHeading360
print("\(modelType!)_\(modelColor!)")
pinView?.image = UIImage(named: "\(modelType!)_\(modelColor!)")?.rotated(by: Measurement(value: modelDirection!, unit: .degrees))
return pinView
}
}
How it looks in the stack call which I am able get :
Here u are , my color is : yellow
(lldb) po dicAnnotations[ann]
▿ Optional<MyModel>
▿ some : <Project.MyModel: 0x101b656f0>
(lldb) po dicAnnotations[ann]?.lineColor
▿ Optional<String>
- some : "yellow"
From :
// viewFor
... else { pinView?.annotation = ann } ...
Any ideas ? It shows me would say "random colored image".
Thanks in advance!
Solution
I've edited the delegate viewFor. Deleted the if pinView == null condition and else as well. Somehow it was not working like it suposed to.
I have been trying to performSegue for annotation, but this code generates different indexes and wrong places are shown in detailsView. Would be great to find out how it is possible to fix this.
Thanks
var detailsDict = [NSDictionary]()
var selectedPlace: NSDictionary!
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
if (view.annotation?.isKind(of: PlaceAnnotation.self))! {
let annotation = view.annotation
let index = (self.mapView.annotations as NSArray).index(of: annotation!)
let place = detailsDict[index]
selectedPlace = place
performSegue(withIdentifier: "showMuseumDetails", sender: self)
}
}
We can do it using this method
func mapView(mapView: MKMapView, didSelectAnnotationView view: MKAnnotationView) {
let annotation = view.annotation
let index = (self.mapView.annotations as NSArray).index(of: annotation!)
print ("Annotation Index = \(index)")
}
I think the main problem here is that you expect that mapView.annotations returns an array with all annotations being in the same order as your detailsDict-Array. I think that this assumption is error prone, because it would highly depend on the internal implementation of the MapKit framework.
A better way would be storing the Dictionary entry together with the PlaceAnnotation that you add to the map:
Add a new property details to class PlaceAnnotation
Set details value when you create the PlaceAnnotation
in calloutAccessoryControlTapped, get back the details property from the PlaceAnnotation
transfer this value to detail controller (maybe via your selectedPlace property, but I would prefer in instatiating the ViewController directly and setting the value).
I am trying to set an image instead of a pin on a map using MapKit. I know I have to set a custom MKAnnotationView. Therefore I should implement the following lines:
func mapView(mapView: MKMapView!, viewForAnnotation annotation: MKAnnotation!) -> MKAnnotationView! {
if !(annotation is CustomPointAnnotation) {
return nil
}
let reuseId = "test"
var anView = mapView.dequeueReusableAnnotationViewWithIdentifier(reuseId)
if anView == nil {
anView = MKAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
anView.canShowCallout = true
}
else {
anView.annotation = annotation
}
//Set annotation-specific properties **AFTER**
//the view is dequeued or created...
let cpa = annotation as CustomPointAnnotation
anView.image = UIImage(named:cpa.imageName)
return anView
}
But it is simply not working in my code. I copy/pasted all the code from this post, adding an image called 1.png to my supporting file. But the map displays a red pin instead of the 1.png image.
It looks like the whole mapView function is useless. Do I have to call it in viewDidLoad? How am I supposed to make it being used? Also, what is the reuseId for? Where should I call it?
Thank you for your answers and happy new year.
Be sure that the MKMapView's delegate is properly linked to the ViewController that implements that method.
You could do this in the "viewWillAppear" method like this: mapView.delegate = self;
Or in the InterfaceBuilder:
- Be sure that your MapKit View is selected
- Go to the "Connections Inspector"
- In the "Outlets" section, Drag and drop the "delegate" attribute to the ViewController that implements the delegate method