I am building an app using mkmapview. I am using custom annotation views to display avatars and corresponding labels. Everything works fine: Each avatar and the corresponding label are displayed properly. I can navigate in the map. But when I zoom out at the maximum, and there are let's say 4 labels being displayed in San Francisco, labels overlap once I get back to original scale. At max zoom out labels are on top of each other. This is normal. But once I get back to normal zoom:
let span = MKCoordinateSpanMake(0.001, 0.001)
Each label looks like it has been stamped by others labels:
The label here above should say "sanchez" but it has melt with other labels texts.
Here is my code for the custom annotation view:
func mapView(mapView: MKMapView!, viewForAnnotation annotation: MKAnnotation!) -> MKAnnotationView! {
if !(annotation is MKPointAnnotation) {
return nil
}
var seleccion:Bool
let reuseId = "test"
var anView = mapView.dequeueReusableAnnotationViewWithIdentifier(reuseId)
if anView == nil {
anView = MKAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
anView.canShowCallout = true
}
else {
anView.annotation = annotation
}
let cpa = annotation as! CustomPointAnnotation
anView.image = cpa.image
if cpa.toBeTriggered == true {
anView.selected = true
}
var nameLbl: UILabel! = UILabel(frame: CGRectMake(-24, 40, 100, 30))
nameLbl.text = cpa.nickName
nameLbl.textColor = UIColor.blackColor()
nameLbl.font = UIFont(name: "Atari Classic Extrasmooth", size: 10)
nameLbl.textAlignment = NSTextAlignment.Center
anView.addSubview(nameLbl)
return anView
}
This is where it gets weird: If I set a background color for nameLbl, labels don't overlap/melt. Which makes me think of a bug from Apple...
EDIT
As @Anna mentionned, CustomPointAnnotation is a data-model class:
import UIKit
import MapKit
class CustomPointAnnotation: MKPointAnnotation {
var image: UIImage!
var toBeTriggered: Bool = false
var selected: Bool = false
var nickName: String!
}
There are two problems causing the label overlap:
As mentioned in the comments, a UILabel
is being added to the annotation view every time viewForAnnotation
is called including when a view is being recycled (when mapView.dequeueReusableAnnotationViewWithIdentifier
returns a view). A recycled view will already have a UILabel
with text set on it. When you add another label on top of that, you get overlapping text.
Instead of adding the label every time, only add it when actually creating an MKAnnotationView
(when the dequeue
returns nil
). Then to set the label's text
, get a reference to the label already on the view (setting a tag
on it is a simple way).
Another approach is to create a custom annotation view class (a subclass of MKAnnotationView
) and implement prepareForReuse
in there and clear the label's text. This custom view class would be a separate thing from the CustomPointAnnotation
model class.
To solve problem 1 in the quickest way, you could do the following:
func mapView(mapView: MKMapView!, viewForAnnotation annotation: MKAnnotation!) -> MKAnnotationView! {
if !(annotation is CustomPointAnnotation) {
//Check for CustomPointAnnotation (not MKPointAnnotation)
//because the code below assumes CustomPointAnnotation.
return nil
}
var seleccion:Bool
let reuseId = "test"
var anView = mapView.dequeueReusableAnnotationViewWithIdentifier(reuseId)
if anView == nil {
anView = MKAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
anView.canShowCallout = true
//Create and add UILabel only when actually creating MKAnnotationView...
var nameLbl: UILabel! = UILabel(frame: CGRectMake(-24, 40, 100, 30))
nameLbl.tag = 42 //set tag on it so we can easily find it later
nameLbl.textColor = UIColor.blackColor()
nameLbl.font = UIFont(name: "Atari Classic Extrasmooth", size: 10)
nameLbl.textAlignment = NSTextAlignment.Center
anView.addSubview(nameLbl)
}
else {
anView.annotation = annotation
}
let cpa = annotation as! CustomPointAnnotation
anView.image = cpa.image
//NOTE: Setting selected property directly on MKAnnotationView
// is not recommended.
// See documentation for the property.
// Instead, call MKMapView.selectAnnotation method
// in the didAddAnnotationViews delegate method.
if cpa.toBeTriggered == true {
anView.selected = true
}
//Get a reference to the UILabel already on the view
//and set its text...
if let nameLbl = anView.viewWithTag(42) as? UILabel {
nameLbl.text = cpa.nickName
}
return anView
}