The goal is to customize the pin colors per some values stored in an structure array.
Per some help here I implemented the the following viewForAnnotation delegate method and that works great calling this delegate method iteratively in a loop based on the size of my structure data array. So it works if I want to set all the pins to one color, purple for example (which is the commented line in the code below).
The problem is when I put in a switch to set the color based on a value in my array it goes through this code but does not respect any of the case values to set it to an alternate color and everything goes to a red pin (seemingly the default). I've printed out the status and debugged to know it is getting inside the switch and setting pinColor's accordingly but they don't seem to stick.
func mapView(aMapView: MKMapView!,
viewForAnnotation annotation: MKAnnotation!) -> MKAnnotationView! {
let theindex = mystructindex // grab the index from a global to be used below
if annotation is MKUserLocation {
//return nil so map view draws "blue dot" for standard user location
return nil
}
let reuseId = "pin"
var pinView = aMapView.dequeueReusableAnnotationViewWithIdentifier(reuseId) as? MKPinAnnotationView
if pinView == nil {
//println("Pinview was nil")
pinView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
pinView!.canShowCallout = true
pinView!.animatesDrop = true
// Preventive if to keep this from being called beyond my arrays index value as the delegate getting called beyond the for loop for some unknown reason
if (theindex < MySupplierData.count) {
// Set the pin color based on the status value in MySupplierData structure array
switch MySupplierData[mystructindex].status {
case 0,1:
println("Case 0 or 1 - setting to Red")
pinView!.pinColor = .Red // Needs help, show red pin
case 2:
println("Case 2 - Setting to Green")
pinView!.pinColor = .Green // Looking Good
case 3:
println("Case 3 - Setting to Purple")
pinView!.pinColor = .Purple // Could use a follow-up
default:
println("Case default - Should Never Happen")
break;
} // end switch
} // end if
// pinView!.pinColor = .Purple // This works fine without the switch and respects any color I set it to.
}
else {
pinView!.annotation = annotation
}
return pinView
}
Inside my for loop within the ViewController I call this as follows, but I don't do anything with the return.
// previous to this I setup some Titles and Subtitle which work fine
self.theMapView.addAnnotation(myAnnotation)
// Call to my mapview
mapView(theMapView, viewForAnnotation: myAnnotation)
I don't do anything with the return Pinview - didn't think I needed to but all the pins get drawn red at this point when using the switch code. Fundamentally I must be missing something here.
7-8-14 Updates to address problems with revised code per Anna's great help/tutoring. TKS!
It almost works, All pins within the Map have the right colors but ones outside of the immediate display are sometimes wrong. Posting all the code involved here since it may help others as this seems to be a very common question on how to do custom work within Maps.
A custom class as suggested to hold other variable in a custom annotation - in this case the status value coming from my data structure, MySupplierData.
class CustomMapPinAnnotation : NSObject, MKAnnotation {
var coordinate: CLLocationCoordinate2D
var title: String
var subtitle: String
var status: Int
init(coordinate: CLLocationCoordinate2D, title: String, subtitle: String, status: Int) {
self.coordinate = coordinate
self.title = title
self.subtitle = subtitle
self.status = status
}
}
The revised mapView - now utilizing the new CustomMapPinAnnotation being passed to it:
func mapView(aMapView: MKMapView!,
viewForAnnotation annotation: CustomMapPinAnnotation!) -> MKAnnotationView! {
let reuseId = "pin"
var pinView = aMapView.dequeueReusableAnnotationViewWithIdentifier(reuseId) as? MKPinAnnotationView
if pinView == nil {
//println("Pinview was nil")
pinView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
pinView!.canShowCallout = true
pinView!.animatesDrop = true
// Code to catch my custom CustomMapPinAnnotation so we can check the status and set the color
if annotation.isKindOfClass(CustomMapPinAnnotation)
{
println("FOUND OUR CustomMapPinAnnotation CLASS IN mapView")
println(" Custom Title = \(annotation.title)")
println(" Custom status passed = \(annotation.status)")
switch annotation.status {
case 0,1:
println("Case 0 or 1 - Setting to Red")
pinView!.pinColor = .Red
case 2:
println("Case 2 - Setting to Green")
pinView!.pinColor = .Green
case 3:
println("Case 3 - Setting to Purple")
pinView!.pinColor = .Purple
default:
println("Case default - Should Never Happen")
break;
} // switch
} // if
}
else {
pinView!.annotation = annotation
}
return pinView
} //func mapView
Within viewDidLoad the setup and For loop to setup the annotations
override func viewDidLoad() {
super.viewDidLoad()
// setup the region and Span
var theSpan:MKCoordinateSpan = MKCoordinateSpanMake(latDelta, longDelta)
// Set the region to the the first element of the structure array.
var theRegion:MKCoordinateRegion = MKCoordinateRegionMake(CLLocationCoordinate2DMake(MySupplierData[0].latitude, MySupplierData[0].longitude), theSpan)
// This set the Map Type (Standard, Satellite, Hybrid)
self.theMapView.mapType = MKMapType.Standard
// Now loop through the structure data from 1 top the end of the structure to map the data
var mytitle: String = ""
var mysubtitle: String = ""
var myCustomPinAnnotation: CustomMapPinAnnotation
for mystructindex = 0; mystructindex < MySupplierData.count; ++mystructindex {
println("INSIDE SUPPLIER LOOP INDEX = \(mystructindex)" )
switch MySupplierData[mystructindex].status {
case 0:
mytitle = "(Red) " + MySupplierData[mystructindex].company
case 1:
mytitle = "(Red) " + MySupplierData[mystructindex].company
case 2:
mytitle = "(Geeen) " + MySupplierData[mystructindex].company
case 3:
mytitle = "(Purple) " + MySupplierData[mystructindex].company
default:
mytitle = "? " + MySupplierData[mystructindex].company
}
mysubtitle = MySupplierData[mystructindex].subtitle
// Create the Custom Annotations with my added status code
myCustomPinAnnotation = CustomMapPinAnnotation(
coordinate: CLLocationCoordinate2DMake(MySupplierData[mystructindex].latitude,MySupplierData[mystructindex].longitude),
title: mytitle, // custom title
subtitle: mysubtitle, // custom subtitle
status: MySupplierData[mystructindex].status) // status that will drive pin color
// put this annotation in the view.
self.theMapView.addAnnotation(myCustomPinAnnotation)
} // For
// This line brings up the display with the specific region in mind, otherwise it seems to default to a US Map.
self.theMapView.setRegion(theRegion, animated: true)
} // viewDidLoad
Debug output shows the For loop executes to completion as expected to create the myCustomPinAnnotation's before the custom viewForAnnotation in mapView gets executed on its own internally. As I move the map to areas outside the immediate view I do note the viewForAnnotation in mapView gets called as needed and I see my switch executing accordingly but the pin colors are not always correct. All the pins within the initial display map are correct every time so it's these outer region ones I am stuck on currently as to why they are off.
First, the code should not be calling viewForAnnotation
explicitly itself.
Remove the explicit call to viewForAnnotation
after the addAnnotation
line.
viewForAnnotation
is a delegate method and the map view will call it automatically when it needs to display an annotation. If it's not getting called automatically, make sure the map view's delegate
property is set (to self
for example).
Second (and the real issue), is that the code assumes that the viewForAnnotation
delegate method will get called only once and immediately after adding each annotation.
This is not the case and is not guaranteed. The map view will call viewForAnnotation
whenever it needs to display the annotation and could be called multiple times for the same annotation or long after the annotation is actually added (eg. after user pans or zooms the map and the annotation comes into view).
See does MKAnnotationView buffer its input queue? for some additional details and relevant links to other answers including sample code.
Basically, you must store the properties that affect an annotation's view with the annotation object itself and retrieve these properties from the annotation
parameter that is passed into viewForAnnotation
.
What I suggest for your case is this:
I assume you are using the built-in annotation class MKPointAnnotation
. Instead of using MKPointAnnotation
which does not let you store your custom status
property alongwith the annotation object itself, either:
Create a custom class that implements the MKAnnotation
protocol but also with a status
property. Set this property when creating the annotation and extract its value from the annotation
parameter passed into viewForAnnotation
and set the pinColor
accordingly. See sample code in linked answer(s).
Make the objects in the MySupplierData
themselves objects that implement the MKAnnotation
protocol. So if the objects in MySupplierData
are instances of some class named, say, Supplier
, make the Supplier
class conform to the MKAnnotation
protocol and then you can add the MySupplierData
objects themselves to the map view when calling addAnnotation
.