How to implement MKClusterAnnotations in Objective-C? - ios

I am trying to make cluster views for annotations that are really close to each other on my Apple Map. I know Apple has native cluster view kit came out with iOS 11, but all tutorials I could find online are written in Swift. I hope somebody could teach me or recommand me any tutorials that i could read to find out how to implement clustered annotations in Objective-C.
My idea is to create a ClusterView class, which inherits the MKAnnotationView class, and then create a instance of the ClusterView in the mapView controller.
I have read the documentation from apple, it only provides you with functions that I might need to call, but it didn't explain how to use them, this is the link to Apple Documentation:https://developer.apple.com/documentation/mapkit/mkclusterannotation?language=objc
Any help would be appreciated!

Here are the basic steps:
Define your annotation view, specifying clusteringIdentifier and collisionMode:
// CustomAnnotationView.h
#import MapKit;
#interface CustomAnnotationView : MKMarkerAnnotationView
#end
and
// CustomAnnotationView.m
#import "CustomAnnotationView.h"
static NSString *identifier = #"com.domain.clusteringIdentifier";
#implementation CustomAnnotationView
- (instancetype)initWithAnnotation:(id<MKAnnotation>)annotation reuseIdentifier:(NSString *)reuseIdentifier {
if ((self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier])) {
self.clusteringIdentifier = identifier;
self.collisionMode = MKAnnotationViewCollisionModeCircle;
}
return self;
}
- (void)setAnnotation:(id<MKAnnotation>)annotation {
[super setAnnotation:annotation];
self.clusteringIdentifier = identifier;
}
#end
Optionally, if you want, you can define your own cluster annotation view, specifying displayPriority and collisionMode. This one also updates the image for the cluster to indicate how many annotations are clustered:
// ClusterAnnotationView.h
#import MapKit;
#interface ClusterAnnotationView : MKAnnotationView
#end
and
// ClusterAnnotationView.m
#import "ClusterAnnotationView.h"
#implementation ClusterAnnotationView
- (instancetype)initWithAnnotation:(id<MKAnnotation>)annotation reuseIdentifier:(NSString *)reuseIdentifier {
if ((self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier])) {
self.displayPriority = MKFeatureDisplayPriorityDefaultHigh;
self.collisionMode = MKAnnotationViewCollisionModeCircle;
}
return self;
}
- (void)setAnnotation:(id<MKAnnotation>)annotation {
super.annotation = annotation;
[self updateImage:annotation];
}
- (void)updateImage:(MKClusterAnnotation *)cluster {
if (!cluster) {
self.image = nil;
return;
}
CGRect rect = CGRectMake(0, 0, 40, 40);
UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:rect.size];
self.image = [renderer imageWithActions:^(UIGraphicsImageRendererContext * _Nonnull rendererContext) {
// circle
[[UIColor blueColor] setFill];
[[UIColor whiteColor] setStroke];
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:rect];
path.lineWidth = 0.5;
[path fill];
[path stroke];
// count
NSString *text = [NSString stringWithFormat:#"%ld", (long) cluster.memberAnnotations.count];
NSDictionary<NSAttributedStringKey, id> *attributes = #{
NSFontAttributeName: [UIFont preferredFontForTextStyle: UIFontTextStyleBody],
NSForegroundColorAttributeName: [UIColor whiteColor]
};
CGSize size = [text sizeWithAttributes:attributes];
CGRect textRect = CGRectMake(rect.origin.x + (rect.size.width - size.width) / 2,
rect.origin.y + (rect.size.height - size.height) / 2,
size.width,
size.height);
[text drawInRect:textRect withAttributes:attributes];
}];
}
#end
You don’t have to create your own subclass for the cluster if you don’t want to. But this just illustrates how you can completely control the appearance of the cluster, should you choose to do so.
Then your view controller just needs to register the appropriate classes and you’re done (no map view delegate needed):
[self.mapView registerClass:[CustomAnnotationView class] forAnnotationViewWithReuseIdentifier:MKMapViewDefaultAnnotationViewReuseIdentifier];
If you want to use your custom clustering view, you can register that, too:
[self.mapView registerClass:[ClusterAnnotationView class] forAnnotationViewWithReuseIdentifier:MKMapViewDefaultClusterAnnotationViewReuseIdentifier];
For example:
// ViewController.m
#import “ViewController.h"
#import MapKit;
#import "CustomAnnotationView.h"
#import "ClusterAnnotationView.h"
#interface ViewController ()
#property (weak, nonatomic) IBOutlet MKMapView *mapView;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self configureMapView];
}
- (void)configureMapView {
self.mapView.userTrackingMode = MKUserTrackingModeFollow;
[self.mapView registerClass:[CustomAnnotationView class] forAnnotationViewWithReuseIdentifier:MKMapViewDefaultAnnotationViewReuseIdentifier];
[self.mapView registerClass:[ClusterAnnotationView class] forAnnotationViewWithReuseIdentifier:MKMapViewDefaultClusterAnnotationViewReuseIdentifier];
}
// I’m going to search for restaurants and add annotations for those,
// but do whatever you want
- (void)performSearch {
MKLocalSearchRequest *request = [[MKLocalSearchRequest alloc] init];
request.naturalLanguageQuery = #"restaurant";
request.region = self.mapView.region;
MKLocalSearch *search = [[MKLocalSearch alloc] initWithRequest:request];
[search startWithCompletionHandler:^(MKLocalSearchResponse * _Nullable response, NSError * _Nullable error) {
if (error) {
NSLog(#"%#", error);
return;
}
for (MKMapItem *mapItem in response.mapItems) {
MKPointAnnotation *annotation = [[MKPointAnnotation alloc] init];
annotation.coordinate = mapItem.placemark.coordinate;
annotation.title = mapItem.name;
annotation.subtitle = mapItem.placemark.thoroughfare;
[self.mapView addAnnotation:annotation];
}
}];
}
#end
That yields:

Here is a basic example as simple as couple of steps
1) Add following annotations within viewDidLoad will work just fine
MKPointAnnotation *point1 = [[MKPointAnnotation alloc] init];
CLLocationCoordinate2D c1;
c1.latitude = 46.469391;
c1.longitude = 30.740883;
point1.coordinate = c1;
point1.title = #"Minsk, Belarus";
[self.mapView addAnnotation:point1];
MKPointAnnotation *point2 = [[MKPointAnnotation alloc] init];
CLLocationCoordinate2D c2;
c2.latitude = 46.469391;
c2.longitude = 30.740883;
point2.coordinate = c2;
point2.title = #"Odessa, Ukraine";
[self.mapView addAnnotation:point2];
2) Within mapView:viewForAnnotation of MKMapViewDelegate provide reusable view for annotations, like so:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation {
if ([annotation isKindOfClass:[MKPointAnnotation class]]) {
MKMarkerAnnotationView* annotationView = (MKMarkerAnnotationView *) (MKMarkerAnnotationView *)[_mapView dequeueReusableAnnotationViewWithIdentifier:#"Jacky.S"];
if (annotationView == nil) {
annotationView = [[MKMarkerAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"Jacky.S"];
annotationView.enabled = YES;
annotationView.clusteringIdentifier = #"pins";
// annotationView.glyphImage = [UIImage imageNamed:#"we can use a nice image instead of the default pins"];
} else {
annotationView.annotation = annotation;
annotationView.clusteringIdentifier = #"pins";
}
return annotationView;
}
return nil;
}
Do not forget to set MKMapViewDelegate to UIViewController
[self.mapView setDelegate:self];
Update Just finished posting a gist shows how to subclass MKMarkerAnnotationView

Related

How to code for MKMapKitView Which shows another container view when user click on pin? [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 7 years ago.
Improve this question
I want this type of output for ios Application. I hadn't any idea how to do it. Please let me know if anyone know how to do it.
EDIT
I had updated my code as per answer, but still it was not working. I can't unserstand that how UIView display and what will be its size ?
#import <UIKit/UIKit.h>
#interface MyView : UIView
#end
#import "MyView.h"
#implementation MyView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
// Initialization code
// initilize all your UIView components
UILabel *label1 = [[UILabel alloc]initWithFrame:CGRectMake(20,30, 200, 44)];
label1.text = #"i am label 1";
[self addSubview:label1]; //add label1 to your custom view
UILabel *label2 = [[UILabel alloc]initWithFrame:CGRectMake(20,80, 200, 44)];
label2.text = #"i am label 2";
[self addSubview:label2]; //add label2 to your custom view
}
return self;
}
=================================================
#import <MapKit/MapKit.h>
#import "MyView.h"
#interface MyCustomView : MKAnnotationView
- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event;
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent*)event;
- (void)setShowCustomCallout:(BOOL)showCustomCallout animated:(BOOL)animated;
- (void)setShowCustomCallout:(BOOL)showCustomCallout
;
#property(strong, nonatomic) MyView *calloutView ;
#end
#import "MyCustomView.h"
#implementation MyCustomView
- (void)setShowCustomCallout:(BOOL)showCustomCallout
{
[self setShowCustomCallout:showCustomCallout animated:NO];
}
- (void)setShowCustomCallout:(BOOL)showCustomCallout animated:(BOOL)animated
{
//if (showCustomCallout == showCustomCallout) return;
showCustomCallout = showCustomCallout;
void (^animationBlock)(void) = nil;
void (^completionBlock)(BOOL finished) = nil;
if (showCustomCallout)
{
self.calloutView.alpha = 0.0f;
animationBlock = ^{
self.calloutView.alpha = 1.0f;
[self addSubview:self.calloutView];
};
} else {
animationBlock = ^{ self.calloutView.alpha = 0.0f; };
completionBlock = ^(BOOL finished) { [self.calloutView removeFromSuperview]; };
}
if (animated) {
[UIView animateWithDuration:0.2f animations:animationBlock completion:completionBlock];
} else {
animationBlock();
completionBlock(YES);
}
}
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
UIView *view = [super hitTest:point withEvent:event];
if ([view isKindOfClass:_calloutView.class]) {
return nil; // todo: add a new delegate method to the map protocol to handle callout taps
} else {
return view;
}
}
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent*)event
{
CGRect rect = self.bounds;
BOOL isInside = CGRectContainsPoint(rect, point);
if(!isInside)
{
for (UIView *view in self.subviews)
{
isInside = CGRectContainsPoint(view.frame, point);
if(isInside)
break;
}
}
return isInside;
}
=================================================
#import "ViewController.h"
#interface ViewController ()
{
CLLocationManager *locationManager;
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
locationManager.distanceFilter = kCLDistanceFilterNone; //whenever we move
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
[locationManager startUpdatingLocation];
[locationManager requestWhenInUseAuthorization]; // Add This Line
[locationManager startUpdatingLocation];
_mapView.showsUserLocation = YES;
_mapView.delegate = self;
CLLocationCoordinate2D annotationCoord;
annotationCoord.latitude = 23.041261;
annotationCoord.longitude = 72.513892;
_mapView.region = MKCoordinateRegionMakeWithDistance(annotationCoord, 800, 800);
// MKCoordinateRegion adjustedRegion = [_mapView regionThatFits:MKCoordinateRegionMakeWithDistance(annotationCoord, 800, 800)];
MKPointAnnotation *annotationPoint = [[MKPointAnnotation alloc] init];
annotationPoint.coordinate = annotationCoord;
annotationPoint.title = #"I am here";
annotationPoint.subtitle = #"Microsoft's headquarters";
}
- (MKAnnotationView *)mapView:(MKMapView *)mapView1 viewForAnnotation:(id <MKAnnotation>)annotation
{
MKPinAnnotationView *annView=[[MKPinAnnotationView alloc]initWithAnnotation:annotation reuseIdentifier:#"pin"];
annView.pinColor = MKPinAnnotationColorGreen;
return annView;
}
- (void)mapView:(MKMapView *)mapView didDeselectAnnotationView:(MKAnnotationView *)view
{
[((MyCustomView *)view) setShowCustomCallout:NO animated:YES];
}
-(void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view
{
MyCustomView *annotationView = [[MyCustomView alloc]init];
[annotationView setShowCustomCallout:YES animated:YES];
}
You have to make a custom view to make a custom callout on mapview.you have to make a class that contain a custom view which will be on pin tapped . Here is the sample code on github.
https://gist.github.com/ShadoFlameX/7495098

How can I add different images instead of pins in MKMapView?

I've been working on an app that has an MKMapView and I want to customize the pins with different images. I've already did that but with one image only, now I need to make a pin show an image and the other pin show another image. How can I do this? If helps, here's my code:
-(MKAnnotationView *)mapView:(MKMapView *)mV viewForAnnotation:(id <MKAnnotation>)annotation{
MKAnnotationView *pinView = nil;
if(annotation != _mapView.userLocation)
{
static NSString *defaultPinID;
pinView = (MKAnnotationView *)[_mapView dequeueReusableAnnotationViewWithIdentifier:defaultPinID];
if ( pinView == nil )
pinView = [[MKAnnotationView alloc]
initWithAnnotation:annotation reuseIdentifier:defaultPinID];
pinView.canShowCallout = YES;
pinView.image = [UIImage imageNamed:#"image1.jpg"];
}
else {
[_mapView.userLocation setTitle:#"Your Location"];
}
return pinView; }
I need the second and the third to show the same image but the first a different one
It follows like this:
- (void)viewWillAppear:(BOOL)animated {CLLocationCoordinate2D First; First.latitude = -12.098970; First.longitude = -77.034531; MKPointAnnotation *annotationPoint = [[MKPointAnnotation alloc] init]; annotationPoint.coordinate = First; annotationPoint.title = #"First"; annotationPoint.subtitle = #"Subtitle1"; [_mapView addAnnotation:annotationPoint];
CLLocationCoordinate2D Second; Second.latitude = -12.098299; Second.longitude = -77.068364; MKPointAnnotation *annotationPoint2 = [[MKPointAnnotation alloc] init]; annotationPoint2.coordinate = Second; annotationPoint2.title = #"Second"; annotationPoint2.subtitle = #"Subtitle2"; [_mapView addAnnotation:annotationPoint2];
CLLocationCoordinate2D Third; Third.latitude = -12.125888; Third.longitude = -77.023346; MKPointAnnotation *annotationPoint3 = [[MKPointAnnotation alloc] init]; annotationPoint3.coordinate = Third; annotationPoint3.title = #"Third"; annotationPoint3.subtitle = #"Subtitle3"; [_mapView addAnnotation:annotationPoint3];}
Make sure you have MapKit.framework and CoreLocation.framework in your project.
My custom pin images are 39 high by 32 wide. Have not tried other sizes but feel free to experiment. My 2 pin images are called pin1.png and pin2.png
Make sure you have your images named correctly to match what is in your code.
In my example I am not using current location but rather a static custom location (thought The Bahamas would be nice for this example). In your project you would of course you the Location Manager to get a user's current location.
I have tested my example and have successfully dropped 2 pins on the map with each pin having its own custom image.
It's not the cleanest code but I only had limited time to write it.
Here is the code for ViewController.h
#import <UIKit/UIKit.h>
#import <MapKit/MapKit.h>
#import <CoreLocation/CoreLocation.h>
#interface ViewController : UIViewController <CLLocationManagerDelegate, MKMapViewDelegate>
#end
Here is the code for ViewController.h
#import "ViewController.h"
#import "MyAnnotation.h"
#import <MapKit/MapKit.h>
#interface ViewController ()
#property (strong, nonatomic) IBOutlet MKMapView *myMapView;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// setup the map view, delegate and current location
[self.myMapView setDelegate:self];
self.myMapView.mapType = MKMapTypeStandard;
CLLocationCoordinate2D myLocation = CLLocationCoordinate2DMake(25.085130,-77.331428);
[self.myMapView setCenterCoordinate:myLocation];
MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(myLocation, 2000, 2000);
region.center = self.myMapView.centerCoordinate;
self.myMapView.showsUserLocation = YES;
[self.myMapView setRegion:region animated:YES];
[self dropPins];
}
-(void)dropPins {
NSMutableArray *annotationArray = [[NSMutableArray alloc] init];
CLLocationCoordinate2D location1 = CLLocationCoordinate2DMake(25.085130, -77.331428);
MyAnnotation *annotation1 = [[MyAnnotation alloc] initWithCoordinates:location1 image:#"pin1.png"];
[annotationArray addObject:annotation1];
[self.myMapView addAnnotations:annotationArray];
[annotationArray removeAllObjects];
CLLocationCoordinate2D location2 = CLLocationCoordinate2DMake(25.085130, -77.336428);
MyAnnotation *annotation2 = [[MyAnnotation alloc] initWithCoordinates:location2 image:#"pin2.png"];
[annotationArray addObject:annotation2];
[self.myMapView addAnnotations:annotationArray];
}
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation {
static NSString *identifier = #"MyLocation";
if ([annotation isKindOfClass:[MyAnnotation class]])
{
MKPinAnnotationView *annotationView = (MKPinAnnotationView *)[self.myMapView dequeueReusableAnnotationViewWithIdentifier:identifier];
if (annotationView == nil)
{
annotationView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:identifier];
} else
{
annotationView.annotation = annotation;
}
annotationView.enabled = YES;
annotationView.canShowCallout = NO;
if([[(MyAnnotation *)annotationView.annotation image] isEqualToString:#"pin1.png"])
annotationView.image = [UIImage imageNamed:#"pin1.png"];
if([[(MyAnnotation *)annotationView.annotation image] isEqualToString:#"pin2.png"])
annotationView.image = [UIImage imageNamed:#"pin2.png"];
return annotationView;
}
return nil;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
Here is the code for MyAnnotation.h
#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
#interface MyAnnotation : NSObject <MKAnnotation>
#property (nonatomic, readonly) CLLocationCoordinate2D coordinate;
#property (nonatomic, copy, readonly) NSString *image;
-(id)initWithCoordinates:(CLLocationCoordinate2D) paramCoordinates
image:(NSString *) paramImage;
#end
Here is the code for MyAnnotation.m
#import "MyAnnotation.h"
#implementation MyAnnotation
-(id)initWithCoordinates:(CLLocationCoordinate2D)paramCoordinates
image:(NSString *)paramImage
{
self = [super init];
if(self != nil)
{
_coordinate = paramCoordinates;
_image = paramImage;
}
return (self);
}
#end

Changing image of annotation pin

This is how I'm adding an annotation on MapKit:
MKPointAnnotation *point = [[MKPointAnnotation alloc] init];
point.coordinate = zoomLocation;
point.title = #"Where am I?";
point.subtitle = #"I'm here!!!";
[self.mapView addAnnotation:point ];
How can change the image of the annotation?
Create a custom annotation class.
in CustomAnnotation.h
#import <MapKit/MapKit.h>
#interface CustomAnnotation : MKAnnotationView
#end
in CustomAnnotation.m
#import "CustomAnnotation.h"
#implementation CustomAnnotation
-(id)initWithAnnotation:(id<MKAnnotation>)annotation reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier];
if (self != nil) {
CGRect frame = self.frame;
frame.size = CGSizeMake(46.0, 49.0);
self.frame = frame;
self.backgroundColor = [UIColor clearColor];
self.centerOffset = CGPointMake(-5, -5);
}
return self;
}
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
// Drawing code
[[UIImage imageNamed:#"IMAGE_NAME"] drawInRect:rect];
}
in our mapview delegate method viewForAnnotation
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation {
static NSString *defaultPinId = #"Pin";
CustomAnnotation *pinView = (CustomAnnotation *)[mapView dequeueReusableAnnotationViewWithIdentifier:defaultPinId];
if (pinView == nil) {
pinView = [[CustomAnnotation alloc]initWithAnnotation:annotation reuseIdentifier:defaultPinId];
}
else {
pinView.annotation = annotation;
}
return pinView;
}
Try this,
MKPointAnnotation *point = [[MKPointAnnotation alloc] init];
[[self.mapView viewForAnnotation:point] setTag:1];
UIImage *image = [UIImage imageNamed:#"pinIcon.png"];
[[self.mapView viewForAnnotation:point] setImage:image];

Problems with MapView Pin Annotation - Pin loses color when map is zoomed/panned/region changes

I have a mapview that displays locations of cash points. Annotations are dropped and the callout can be clicked on to go to a page with more detail about that location. There are two categories of cashpoint, free and paid, free cashpoint pins are green and the other red. When the pins drop they are the correct colours. Everything works fine untill i zoom to user location or other areas of the map then when i go back to the pins they have lost their colour formatting and are all the original red colour.
I assume this is something to do with the map reloading when it downloads tiles and not reloading the pins properly, although i could be wrong.
Any help will be much appreciated.
here is my code:
#import "CashPointMapViewController.h"
#import "PinDrop.h"
#import "CashPointDetailViewController.h"
#implementation CashPointMapViewController
#synthesize app, theCashList, mapview, ann, count, myArray, pinColor;
- (IBAction) getlocation {
MKCoordinateRegion region;
region.center = self.mapview.userLocation.coordinate;
region.span.longitudeDelta = 0.01f;
region.span.longitudeDelta = 0.01f;
[mapview setRegion:region animated:YES];
}
- (void)viewDidLoad {
[super viewDidLoad];
mapview.showsUserLocation = YES;
[mapview setMapType:MKMapTypeStandard];
[mapview setZoomEnabled:YES];
[mapview setScrollEnabled:YES];
MKCoordinateRegion region = { {0.0, 0.0 }, {0.0, 0.0 } };
region.center.latitude = 53.801279;
region.center.longitude = -1.548567;
region.span.longitudeDelta = 0.3f;
region.span.longitudeDelta = 0.3f;
[mapview setRegion:region animated:YES];
app = [[UIApplication sharedApplication]delegate];
UIImage *locate = [UIImage imageNamed:#"location arrow white.png"];
UIBarButtonItem *userlocatebutton = [[UIBarButtonItem alloc] initWithImage:locate style:UIBarButtonItemStylePlain target:self action:#selector(getlocation)];
self.navigationItem.rightBarButtonItem = userlocatebutton;
[self performSelectorInBackground:#selector(annloop) withObject:self];
}
-(void) annloop {
int i;
for (i=0; i<=count-1; i = i+1) {
theCashList = [myArray objectAtIndex:i];
NSString *trimlat = [theCashList.lat stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSString *trimlon = [theCashList.lon stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
double latdouble = [trimlat doubleValue];
double londouble = [trimlon doubleValue];
CLLocationCoordinate2D coord = {(latdouble),(londouble)};
ann = [[PinDrop alloc] init];
ann.index = i;
ann.title = [theCashList.name stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSString *street = [theCashList.street stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSString *town = [theCashList.town stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSString *postcode = [theCashList.postcode stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSString *address = [[NSString alloc] initWithFormat:#"%#, %#, %#", street, town, postcode];
ann.subtitle = address;
ann.coordinate = coord;
NSString *trimprice = [theCashList.price stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
if ([trimprice isEqualToString:#"Free"])
{
ann.price = 1;
}
else
{
ann.price = 0;
}
[mapview performSelectorOnMainThread:#selector(addAnnotation:) withObject:ann waitUntilDone:YES];
}
}
-(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation {
if ([annotation isKindOfClass:[MKUserLocation class]])
return nil;
MKPinAnnotationView *mypin = [[MKPinAnnotationView alloc]initWithAnnotation:ann reuseIdentifier:#"current"];
mypin.backgroundColor = [UIColor clearColor];
UIButton *goToDetail = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
mypin.rightCalloutAccessoryView = goToDetail;
mypin.draggable = NO;
mypin.animatesDrop = TRUE;
mypin.canShowCallout = YES;
if (ann.price == 1)
{
mypin.pinColor = MKPinAnnotationColorGreen;
}
else
{
mypin.pinColor = MKPinAnnotationColorRed;
}
return mypin;
}
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view
calloutAccessoryControlTapped:(UIControl *)control {
PinDrop *annView = view.annotation;
CashPointDetailViewController *detailView = [[CashPointDetailViewController alloc]init];
theCashList = [myArray objectAtIndex:annView.index];
detailView.theCashList = theCashList;
[self.navigationController pushViewController:detailView animated:YES];
}
- (void)viewDidUnload {
[super viewDidUnload];
}
- (BOOL)shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation)interfaceOrientation {
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
#end
EDIT: Here is my .h if it helps.
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
#import "CashPointList.h"
#import <MapKit/MapKit.h>
#import "PinDrop.h"
#interface CashPointMapViewController : UIViewController {
MKMapView *mapview;
PinDrop *ann;
}
#property (nonatomic, retain) AppDelegate *app;
#property (nonatomic, retain) CashPointList *theCashList;
#property (nonatomic, retain) PinDrop *ann;
#property (nonatomic, retain) IBOutlet MKMapView *mapview;
#property (nonatomic, readwrite) int count;
#property (nonatomic, retain) NSMutableArray *myArray;
#property (nonatomic) MKPinAnnotationColor pinColor;
-(IBAction) getlocation;
#end
It's not a problem related to the map reloading tiles.
The issue is that the code in the viewForAnnotation delegate is using the ann object with the incorrect assumption that the class-instance-level ann object will be in sync with whenever the delegate method is called.
The viewForAnnotation delegate is not necessarily called in the order that you add annotations and can be called multiple times for the same annotation if the map needs to re-display an annotation when it comes back into view.
When the delegate method gets called again for a previously added annotation, ann and annotation no longer point to the same object. ann is now probably pointing to the last added annotation and so all the annotations change to its color.
In that delegate method, you must use the annotation parameter which is a reference to the annotation that the map view wants the view for in the current call (which may be completely unrelated to your outside loop).
So this line:
MKPinAnnotationView *mypin = [[MKPinAnnotationView alloc]
initWithAnnotation:ann reuseIdentifier:#"current"];
should be:
MKPinAnnotationView *mypin = [[MKPinAnnotationView alloc]
initWithAnnotation:annotation reuseIdentifier:#"current"];
^^^^^^^^^^
and when checking the annotation's properties, use the annotation parameter (and cast it to your custom class to get at the custom properties):
PinDrop *ann = (PinDrop *)annotation;
//Note that this local "ann" is NOT the same as the class-level "ann".
//May want to use a different name to avoid confusion.
//The compiler may also warn you about this.
if (ann.price == 1)
...
A separate, unrelated, but highly recommended suggestion is to implement annotation view re-use by using dequeueReusableAnnotationViewWithIdentifier. This will improve performance when you have lots of annotations.

MKMapKit UILabel as an annoation

I thought this would be something that's fairly common - but cant find it anywhere.
I know how to put images on a map. I use CSMapAnnotation found here http://spitzkoff.com/craig/?p=81.
I've pretty much taken the CSMapAnnotationTypeImage as an example and made a CSMapAnnotationTypeLabel but it keep just showing pins on the map and not the UILabel like I expected.
the header file
#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
#interface CSLabelAnnotationView : MKAnnotationView {
UILabel* _label;
}
#property(retain, nonatomic) UILabel* label;
#end
and the source file
#import "CSLabelAnnotationView.h"
#import "CSMapAnnotation.h"
#import "util.h"
#implementation CSLabelAnnotationView
#synthesize label = _label;
#define kWidth 120
#define kHeight 20
- (id)initWithAnnotation:(id <MKAnnotation>)annotation reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier];
self.frame = CGRectMake(0, 0, kWidth, kHeight);
self.backgroundColor = [UIColor clearColor];
CSMapAnnotation* csAnnotation = (CSMapAnnotation*)annotation;
_label = [[UILabel alloc] initWithFrame:self.frame];
_label.text=csAnnotation.title;
[self addSubview:_label];
return self;
}
#if 0
- (void)prepareForReuse
{
TGLog(#"");
[_imageView removeFromSuperview];
[_imageView release];
}
#endif
-(void) dealloc
{
[_label release];
[super dealloc];
}
#end
to add it to the map
annotation = [[[CSMapAnnotation alloc] initWithCoordinate:coordinate
annotationType:CSMapAnnotationTypeLabel
title:i.name
reuseIdentifier:#"ocualabel"] autorelease];
[_mapView addAnnotation:annotation];
and
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
{
MKAnnotationView* annotationView = nil;
if (annotation == mapView.userLocation){
return nil; //default to blue dot
}
// determine the type of annotation, and produce the correct type of annotation view for it.
CSMapAnnotation* csAnnotation = (CSMapAnnotation*)annotation;
TGLog(#"csAnnotation.annotationType %d", csAnnotation.annotationType);
if(csAnnotation.annotationType == CSMapAnnotationTypeStart ||
csAnnotation.annotationType == CSMapAnnotationTypeEnd)
{
NSString* identifier = #"Pin";
MKPinAnnotationView* pin = (MKPinAnnotationView*)[self.mapView dequeueReusableAnnotationViewWithIdentifier:identifier];
if(nil == pin)
{
pin = [[[MKPinAnnotationView alloc] initWithAnnotation:csAnnotation reuseIdentifier:identifier] autorelease];
}
[pin setPinColor:(csAnnotation.annotationType == CSMapAnnotationTypeEnd) ? MKPinAnnotationColorRed : MKPinAnnotationColorGreen];
annotationView = pin;
TGLog(#"csPin anno");
}
else if(csAnnotation.annotationType == CSMapAnnotationTypeImage)
{
NSString* identifier = csAnnotation.reuseIdentifier;
CSImageAnnotationView* imageAnnotationView = (CSImageAnnotationView*)[self.mapView dequeueReusableAnnotationViewWithIdentifier:identifier];
if(nil == imageAnnotationView)
{
imageAnnotationView = [[[CSImageAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:identifier] autorelease];
imageAnnotationView.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
}
TGLog(#"CSImage anno");
annotationView = imageAnnotationView;
} else if(csAnnotation.annotationType == CSMapAnnotationTypeLabel)
{
NSString* identifier = csAnnotation.reuseIdentifier;
CSLabelAnnotationView* labelAnnotationView = (CSLabelAnnotationView*)[self.mapView dequeueReusableAnnotationViewWithIdentifier:identifier];
if(nil == labelAnnotationView)
{
labelAnnotationView = [[[CSLabelAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:identifier] autorelease];
}
TGLog(#"CSLabel anno");
} else {
TGLog(#"%d", csAnnotation.annotationType);
}
[annotationView setEnabled:YES];
[annotationView setCanShowCallout:YES];
return annotationView;
}
Can anyone help me with this? Or is there some other standard way to get UILabels on a map?
Found it.
Needed to add this in viewForAnnotation():
annotationView = labelAnnotationView;

Resources