MKMapView refresh after pin moves - iphone

A custom AnnotationView is updated with new coordinates. But the problem is that it visually updates only after some manipulations with MKMapView, e.g. zooming or moving.
What should I do to manually update visual position on a map?
PS. I've tried to change region to current map's region. But it does change zoom. It's strange.
[mapView setRegion:[mapView region] animated:YES];

I am a little shoked after hours of research. The answer is just:
[mapView setCenterCoordinate:mapView.region.center animated:NO];
Do not ask me why, but it updates a mapview and it's what i was need.

MKMapView observes the coordinate property of annotations via KVO. You simply need to observe proper KVO protocol and send the annotation willChangeValueForKey: and didChangeValueForKey: with keypath of #"coordinate" before and after you update the coordinates.
Likewise title and subtitle are also observed by MKMapView. so if you update those and want the value in the callout to change automatically without any effort on your part, just do the same: call willChangeValueForKey: and didChangeValueForKey:

if your adding your annoations from a thread it wont work.
i had the same problem and just wrapping my function that was adding the annotations with the following worked
[self performSelectorOnMainThread:#selector(addCameraIconOnMain:) obj waitUntilDone:true];
-(void) addCameraIconOnMain:(myobjecttype*)obj
{
// this isnt the entire function, customize for your own purpose.....
[mapView addAnnotation:annotation];
}

The answer here is NOT to refresh the MapView or the Annotation!
the coordinate property of MKAnnotation has KVO on it. If you just add the id pointer, of the object you want on the map, to the mapview and update the coordinate property with a new location, MKMapView will do the rest for you.
As close as you can get to a free lunch!

I solved this error with an asynchronous call, at least 0.5 delay.
e.g.: [self performSelector:#selector(redrawPins) withObject:nil afterDelay:0.5];
Where "redrawPins" is the function which adds and removes pins.

There's no reason you can't remove and then re-add the annotation. That's probably way more performant than moving the entire map, even if its a fake move.

Here is the interface to MapAnnotation:
// CSMapAnnotation.h
// mapLines
// Created by Craig on 5/15/09.
// Copyright 2009 Craig Spitzkoff. All rights reserved.
#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
// types of annotations for which we will provide annotation views.
typedef enum {
MapAnnotationTypeStart = 0,
MapAnnotationTypeEnd = 1,
MapAnnotationTypeImage = 2
} MapAnnotationType;
#interface MapAnnotation : NSObject <MKAnnotation>
{
CLLocationCoordinate2D _coordinate;
MapAnnotationType _annotationType;
NSString* _title;
NSString* _subtitle;
NSString* _userData;
NSString* speed;
NSString* identifier;
}
#property (nonatomic, retain) NSString *speed;
#property (nonatomic, retain) NSString *identifier;
-(id) initWithCoordinate:(CLLocationCoordinate2D)coordinate
annotationType: (MapAnnotationType) annotationType
title: (NSString*) title
subtitle: (NSString*) subtitle
speed: (NSString *) speed
identifier: (NSString *) identifier;
-(id) setWithCoordinate: (CLLocationCoordinate2D) coordinate
annotationType: (MapAnnotationType) annotationType
title: (NSString*) title
subtitle: (NSString*) subtitle
speed: (NSString*) speed
identifier: (NSString*) identifier;
#property MapAnnotationType annotationType;
#property (nonatomic, retain) NSString* userData;
#end
And here is the implementation:
// CSMapAnnotation.m
// mapLines
// Created by Craig on 5/15/09.
// Copyright 2009 Craig Spitzkoff. All rights reserved.
#import "MapAnnotation.h"
#implementation MapAnnotation
#synthesize coordinate = _coordinate;
#synthesize annotationType = _annotationType;
#synthesize userData = _userData;
#synthesize speed;
#synthesize identifier;
-(id) initWithCoordinate:(CLLocationCoordinate2D)coordinate
annotationType: (MapAnnotationType) annotationType
title: (NSString*)title
subtitle: (NSString*) subtitle
speed: (NSString *) speedz
identifier: (NSString *) identifierz
{
self = [super init];
_coordinate = coordinate;
_title = [title retain];
_subtitle = [subtitle retain];
_annotationType = annotationType;
speed = speedz;
identifier = identifierz;
return self;
}
-(id) setWithCoordinate:(CLLocationCoordinate2D)coordinate
annotationType: (MapAnnotationType) annotationType
title: (NSString*) title
subtitle: (NSString*) subtitle
speed: (NSString*) speedz
identifier: (NSString*) identifierz
{
_coordinate = coordinate;
_title = [title retain];
_subtitle = [subtitle retain];
_annotationType = annotationType;
speed = speedz;
identifier = identifierz;
return self;
}
-(NSString*) title
{
return _title;
}
-(NSString*) subtitle
{
return _subtitle;
}
-(void) dealloc
{
[_title release];
[_userData release];
[super dealloc];
}
#end

Related

Using a custom annotation when adding it to map

I'm having trouble accessing my custom annotation class when adding it to a mapview. I have the custom class working properly but i when i add it to the map i'm not sure how to access the custom annotation through this delegate:
- (MKAnnotationView *) mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
i've tried looking online and havent found anything. any help would be great.
Its called like this:
CLLocationCoordinate2D coords = CLLocationCoordinate2DMake(shops.latitude, shops.longitude);
AnnotationForId *shop = [[AnnotationForId alloc] initWithCoordinate:coords];
//[[CLLocationCoordinate2DMake(shops.latitude, shops.longtitude)]];
shop.title = shops.name;
shop.subtitle = #"Coffee Shop";
shop.shopId = shops.id;
[map addAnnotation:shop];
This is Simple Example For how to Create Custom AnnotationView.
Create custom AnnotationView:
#import <MapKit/MapKit.h>
#interface AnnotationView : MKPlacemark
#property (nonatomic, readwrite, assign) CLLocationCoordinate2D coordinate;
#property (nonatomic, strong) NSString *title;
#property (nonatomic, strong) NSString *subtitle;
// you can put here any controllers that you want. (such like UIImage, UIView,...etc)
#end
And in .m file
#import "AnnotationView.h"
#implementation AnnotationView
- (id)initWithCoordinate:(CLLocationCoordinate2D)coordinate addressDictionary:(NSDictionary *)addressDictionary
{
if ((self = [super initWithCoordinate:coordinate addressDictionary:addressDictionary]))
{
self.coordinate = coordinate;
}
return self;
}
#end
// Use Annotation Add #import "AnnotationView.h" in your relevant .m file:
CLLocationCoordinate2D pCoordinate ;
pCoordinate.latitude = LatValue;
pCoordinate.longitude = LanValue;
// Create Obj Of AnnotationView class
AnnotationView *annotation = [[AnnotationView alloc] initWithCoordinate:pCoordinate addressDictionary:nil] ;
annotation.title = #"I m Here";
annotation.subtitle = #"This is Sub Tiitle";
[self.mapView addAnnotation:annotation];
Test the annotation to see if it's the same class as your custom class:
- (MKAnnotationView *) mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
MKAnnotationView *mkav = nil;
if ([annotation isKindOfClass:[AnnotationForId class]])
{
// This should be safe now.
AnnotationForId *aid = annotation;
// Whatever you wanted to add, including making your own view.
}
return mkav;
}

MKMapView - Removing annotation causes app crash

Removing annotations from my map view in the following way:
if ([[self.mapView annotations] count] > 0)
{
[self.mapView removeAnnotations:[self.mapView annotations]];
}
causes my application to crash with the following exception:
*** Terminating app due to uncaught exception 'NSRangeException', reason: 'Cannot remove an observer <MKAnnotationContainerView 0xe87b420> for the key path "title" from <PFAnnotation 0x10851230> because it is not registered as an observer.'
The annotations are added in the following way:
CLLocationCoordinate2D pinPosition;
for (int index = 0; index < [array count]; index++)
{
Station *aStation = [array objectAtIndex:index];
PFAnnotation *stationPin = [[PFAnnotation alloc] init]; //StationPinView
pinPosition = CLLocationCoordinate2DMake([[aStation valueForKey:#"latitude"] doubleValue], [[aStation valueForKey:#"longitude"] doubleValue]);
stationPin.stationName = [aStation valueForKey:#"stationName"];
stationPin.stationPosition = pinPosition;
stationPin.stationLength = [aStation valueForKey:#"platformLength"];
[self.mapView addAnnotation:stationPin];
[stationPin release];
}
My PFAnnotation.h is:
#interface PFAnnotation : NSObject <MKAnnotation>
{
NSString *stationName;
CLLocationCoordinate2D stationPosition;
NSNumber *stationLength;
}
#property (nonatomic, retain) NSString *stationName;
#property CLLocationCoordinate2D stationPosition;
#property (nonatomic, retain) NSNumber *stationLength;
#end
and my PFAnnotation.m is:
#implementation PFAnnotation
#synthesize stationName;
#synthesize stationPosition;
#synthesize stationLength;
- (CLLocationCoordinate2D)coordinate;
{
return stationPosition;
}
- (NSString *)title
{
return stationName;
}
- (NSString *)subtitle
{
if (stationLength == nil)
return nil;
else
return [NSString stringWithFormat:#"Platform Length: %#ft",stationLength];
}
- (void)dealloc {
[stationName release];
[stationLength release];
[super dealloc];
}
I have read in some other threads that, setting the annotation properties from background thread is the reason for the above error. But in my case,it is not so because every thing is performed on the main thread. Please advise.
ok..solved it at last!!! I think it was due to the animation provided during addition of annotations. since there were a number of annotations that were added back-to-back with animation and also the annotations were removed just before the animation started, there could have been a reference to the released annotation(this is my guess). Moreover, the removal+addition process was made on each regionDidChangeAnimated call, which could have made a overlap between the removal and addition process. Anyway, how I solved it was that, I provided a timer which will be fired only after 1 second after every regionDidchangeAnimated to make sure that the user has done with dragging. Thus unnecessary addition+removal of annotations was avoided and I was able to avoid the crash. Thanks to all guys here for their time taken to support me, especially Guntis Treulands.
In your PFAnnotation class, did you declare both title and subtitle properties as they are in the protocol?
http://developer.apple.com/library/ios/#documentation/MapKit/Reference/MKAnnotation_Protocol/Reference/Reference.html
If Your PFAnnotation really has incorrect setters getters for string values:
from here: http://cocoadevcentral.com/d/learn_objectivec/
Setter:
- (void) setCaption: (NSString*)input
{
[caption autorelease];
caption = [input retain];
}
Getter:
- (NSString*) caption
{
return caption;
}
Release:
- (void) dealloc
{
[caption release];
[super dealloc];
}
also - it's easier to provide coordinates in this way: (also works on ios 3.1.3)
stationPin.stationPosition = (CLLocationCoordinate2D) {[[aStation valueForKey:#"latitude"] doubleValue], [[aStation valueForKey:#"longitude"] doubleValue]}
than (only from ios 4)
stationPin.stationPosition = CLLocationCoordinate2DMake([[aStation valueForKey:#"latitude"] doubleValue], [[aStation valueForKey:#"longitude"] doubleValue]);
Please check whether an explicit removal of observer to the property "title" is being done anywhere in the code.

Unrecognized selector sent to instance?

I have seen that a few others have had this problem as well.
I'm trying to follow a tutorial online that shows how to create animated pins on a MapView.
I have implemented the code as shown in the tutorial and the project builds fine except I receive this exception:
-[MKPointAnnotation iconN]: unrecognized selector sent to instance
I have a subclass of 'MKPinAnnotationView' and in the .m file I create this method:
- (void)setAnnotation:(id<MKAnnotation>)annotation {
[super setAnnotation:annotation];
//Place *place = [[Place alloc] init];
Place *place = (Place *)annotation;
//The following line is where the program sends "SIGABRT"
icon = [UIImage imageNamed:[NSString stringWithFormat:#"pin_%d.png", [place.iconN intValue]]];
[iconView setImage:icon];
}
Here are a few parts from my "model" which is called Place.h/.m.
Here is where I create the property for 'iconN'.
#property (retain, nonatomic) NSNumber *iconN;
And here I synthesize it:
#synthesize iconN = _iconN;
Any help is greatly appreciated.
EDIT:
Here is the Place.h and Place.m
#import <MapKit/MapKit.h>
#import <CoreLocation/CoreLocation.h>
#interface Place : NSObject <MKAnnotation> {
CLLocationCoordinate2D coordinate;
NSString *title;
}
#property (retain, nonatomic) NSNumber *iconN;
#property (nonatomic, copy) NSString *title;
#property (nonatomic) CLLocationCoordinate2D coordinate;
- (id)initWithLong:(CGFloat)lon Lat:(CGFloat)lat iconNumber:(NSNumber *)iconNumber;
#end
And the Place.m
#import "Place.h"
#implementation Place
#synthesize coordinate;
#synthesize iconN = _iconN;
#synthesize title;
- (id)initWithLong:(CGFloat)lon Lat:(CGFloat)lat iconNumber:(NSNumber *)iconNumber {
self = [super init];
if (self) {
coordinate = CLLocationCoordinate2DMake(lat, lon);
self.iconN = iconNumber;
}
return self;
}
- (NSString *)title {
return [NSString stringWithFormat:#"Bus: %d", [self.iconN intValue]];
}
- (NSString *)subtitle {
return [NSString stringWithFormat:#"bus[%d] from database.", [self.iconN intValue] - 1];
}
#end
You cannot convert a MKAnnotation to a Place just by casting it. This line is wrong.
Place *place = (Place *)annotation;
You should post your Place.h and Place.m files if you're still stuck. You need to either set the iconN property on a new Place object, or create an init method in the Place class that accepts the MKAnnotation object as a parameter and sets it own internal values accordingly.
In the line
Place *place = (Place *)annotation;
has the variable place of annotation variable class (MKPointAnnotation), you are not able to bring the master class variable to a subclass in this way. Instead you'll have to make a constructor for Place from MKPointAnnotation and perform a check in the setAnnotation method that annotation is of MKPointAnnotation.
You are sending the message to the annotation but you seem to have subclasses the annotation view.
Posting as an answer what was originally just a comment:
I'm not familiar with the MapKit, but the thing that sticks out for me in this: -[MKPointAnnotation iconN]: unrecognized selector sent to instance is that the class is MKPointAnnotation. So the annotation you're receiving isn't actually a Place object, it's an MKPointAnnotation object - you can't just cast to Place. I suspect the root of your problem is where you create your annotation object in the first place.

Regarding Apple's KMLViewer placemarkDescription and annotation subtitle

In my app I am using Apple's KMLViewer to show annotations that I get from a KML file.In the file KMLParser.m, there is an instance variable, placemarkDescription that converts the information under Description tags from kml file to annotation subtitle.Now, in my file every annotation has the information stored under Description in this way:
<table width="280px"><tr><td></td><td></td></tr></table><table width="280px"><tr><td><b>Fitness Bulls</b>---Palester sportive. Sporti dhe koha e lire.....<a href="http://www.site.com/BIZ_DIR/810180432/Article-Fitness-Bulls.aspx" style="color:Green;" >Shikoni detajet >></a></td></tr><tr><td><a href="http://www.site.com/HartaV2/AddReview.aspx?gisDataId=8123855e-b798-40bc-ad2e-00346a931211" style="color:Green;" >Shkruani pershtypjen tuaj >> </a> <p style="float:right;">Postuar nga:<i>Import</i></p></td></tr></table>
In KMLParser.m i have transformed the placemarkDescription from that to this:
<html><body>
<table width="280px"><tr><td></td><td></td></tr></table><table width="280px"><tr><td>
<b>Fitness Bulls</b>---Palester sportive. Sporti dhe koha e lire.....
<a href="http://www.site.com/BIZ_DIR/810180432/Article-Fitness-Bulls.aspx" style="color:Green;" >Shikoni detajet >></a></td></tr><tr><td>
<a href="http://www.site.com/HartaV2/AddReview.aspx?gisDataId=8123855e-b798-40bc-ad2e-00346a931211" style="color:Green;" >Shkruani pershtypjen tuaj >> </a> <p
style="float:right;">Postuar nga:<i>Import</i></p></td></tr></table>
</body></html>
I've done this because I want to pas this string to a webView and visualize this in it.
The problem is that when the kml loads, the methods get the description information, get called severe times.Exactly as times as placemarks stored in the kml.So passing the string directly has no effect.If i choose to set active the subtitle option (annotation.subtitle = placemarkDescription in KMLParser), maybe I gen get the subtitle info of the annotation the user tapped, but I don't want to show this information because it shows like this
<table width="280px"><tr><td></td><td></td></tr></table><table width="280px"><tr......
By the way, I don't have any idea of how to get the subtitle info of the selected annotation.
So far, I have managed only to store the description information in an array (done this in the KMLParser.m).But what should I do with that array?How to know to which array entry corresponds the annotation the user tapped (the annotation that has the callout bubble opened).
So I don't know what to do.
Maybe I have not been too clear: What I want to do is get the Description information of a placemark (annotation), when the user taps an annotation in the map, tapping the disclosureButton should redirect him to a webView that shows the description Information.
EDIT code Added:
DetailViewController.h
#import <UIKit/UIKit.h>
#interface DetailViewController : UIViewController<UIWebViewDelegate> {
UIWebView *webView;
UITextField *addressBar;
UIActivityIndicatorView *activityIndicator;
NSString *placemarkDescription;
}
#property (nonatomic, retain) IBOutlet UIWebView *webView;
#property (nonatomic, retain) IBOutlet UITextField *addressBar;
#property (nonatomic, retain) IBOutlet UIActivityIndicatorView *activityIndicator;
#property (nonatomic, retain) NSString *placemarkDescription;
-(IBAction) gotoAddress:(id)sender;
-(IBAction) goBack:(id)sender;
-(IBAction) goForward:(id)sender;
#end
DetailViewController.m
#import "DetailViewController.h"
#implementation DetailViewController
#synthesize webView, addressBar, activityIndicator, placemarkDescription;
- (void)viewDidLoad {
[super viewDidLoad];
[webView loadHTMLString:placemarkDescription baseURL:nil];
}
-(IBAction)gotoAddress:(id) sender {
NSURL *url = [NSURL URLWithString:[addressBar text]];
NSURLRequest *requestObj = [NSURLRequest requestWithURL:url];
[webView loadRequest:requestObj];
[addressBar resignFirstResponder];
}
-(IBAction) goBack:(id)sender {
[webView goBack];
}
-(IBAction) goForward:(id)sender {
[webView goForward];
}
- (BOOL)webView:(UIWebView*)webView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType {
if (navigationType == UIWebViewNavigationTypeLinkClicked) {
NSURL *URL = [request URL];
if ([[URL scheme] isEqualToString:#"http"]) {
[addressBar setText:[URL absoluteString]];
[self gotoAddress:nil];
}
return NO;
}
return YES;
}
- (void)webViewDidStartLoad:(UIWebView *)webView {
[activityIndicator startAnimating];
}
- (void)webViewDidFinishLoad:(UIWebView *)webView {
[activityIndicator stopAnimating];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
- (void)dealloc {
[super dealloc];
}
#end
PlacemarkAnnotation2.h
#import <Foundation/Foundation.h>
#import <MapKit/Mapkit.h>
#interface PlacemarkAnnotation2 : NSObject <MKAnnotation> {
CLLocationCoordinate2D coordinate;
NSString * title;
NSString * subtitle;
NSString * placemarkDescription;
}
#property (nonatomic, assign) CLLocationCoordinate2D coordinate;
#property (nonatomic, retain) NSString * title;
#property (nonatomic, retain) NSString * subtitle;
#property (nonatomic, retain) NSString * placemarkDescription;
#end
PlacemarkAnnotation2.m
#import "PlacemarkAnnotation2.h"
#implementation PlacemarkAnnotation2
#synthesize coordinate, title, subtitle, placemarkDescription;
- (id) initWithCoordinate:(CLLocationCoordinate2D)coord andTitle:(NSString *)maintitle andSubtitle:(NSString *)subTitle {
self.coordinate = coord;
self.title = maintitle;
self.subtitle = subTitle;
return self;
}
-(NSString *) placemarkDescription
{
return placemarkDescription;
}
- (void) setPlacemarkDescription: (NSString *) pd
{
placemarkDescription = pd;
}
- (void) dealloc {
[title dealloc];
[subtitle dealloc];
[placemarkDescription dealloc];
[super dealloc];
}
#end
Changes in KMLParser.M
//KMLPoint class
- (MKShape *)mapkitShape
{
PlacemarkAnnotation2 *annotation = [[PlacemarkAnnotation2 alloc] init];
annotation.coordinate = point;
return [annotation autorelease];
}
//KMLPlacemark class
- (void)_createShape
{
if (!mkShape) {
mkShape = [[geometry mapkitShape] retain];
mkShape.title = name;
// Skip setting the subtitle for now because they're frequently
// too verbose for viewing on in a callout in most kml files.
NSString *lessThan = #"<";
NSString *greaterThan = #">";
placemarkDescription = [placemarkDescription stringByReplacingOccurrencesOfString:lessThan
withString:#"<"];
placemarkDescription = [placemarkDescription stringByReplacingOccurrencesOfString:greaterThan
withString:#">"];
NSString *beforeBody = #"<html><body>";
NSString *afterBody = #"</body></html>";
NSString *finalContent = [[beforeBody stringByAppendingString:placemarkDescription]
stringByAppendingString:afterBody];
placemarkDescription = finalContent;
mkShape.placemarkDescription = placemarkDescription;
}
}
Error found in this lines of code (No cause of crash description):
-(void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view
calloutAccessoryControlTapped:(UIControl *)control
{
NSLog(#">>> Entering %s <<<", __PRETTY_FUNCTION__);
DetailViewController *dvc = [[DetailViewController alloc] initWithNibName:#"DetailViewController" bundle:[NSBundle mainBundle]];
PlacemarkAnnotation2 *pa = (PlacemarkAnnotation2 *)view.annotation;
dvc.placemarkDescription = pa.placemarkDescription;
[self presentModalViewController:dvc animated:YES];
[dvc release];
NSLog(#"<<< Leaving %s >>>", __PRETTY_FUNCTION__);
}
It's not clear how much of the KMLViewer sample app code you're using but one way to do this is to create your own annotation class instead of using the MKPointAnnotation class like the sample app does.
The custom class (eg. "PlacemarkAnnotation"), should implement the MKAnnotation protocol or be a sub-class of MKShape (if you are using the KMLViewer code). In the custom class, add a placemarkDescription property.
Where the KMLViewer code currently creates an MKPointAnnotation object, create a PlacemarkAnnotation instead and set its placemarkDescription property instead of the subtitle property.
Then in the viewForAnnotation delegate method, set the rightCalloutAccessoryView to a detail disclosure button.
Next, add to the project a detail view controller with a UIWebView in it. Add a placemarkDescription property to the view controller. In the viewDidLoad method, call loadHTMLString on the web view and pass it placemarkDescription (I think you can pass nil for the baseURL).
In the map view's calloutAccessoryControlTapped delegate method, create the detail view controller, set its placemarkDescription property and present it:
-(void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view
calloutAccessoryControlTapped:(UIControl *)control
{
DetailViewController *dvc = [[DetailViewController alloc] init...
PlacemarkAnnotation *pa = (PlacemarkAnnotation *)view.annotation;
dvc.placemarkDescription = pa.placemarkDescription;
[self presentModalViewController:dvc animated:YES];
[dvc release];
}
Edit:
First, it looks like it will be best to subclass MKShape for your custom class instead of implementing the MKAnnotation protocol. The rest of the KMLViewer code is based on this assumption. So change #interface PlacemarkAnnotation2 : NSObject <MKAnnotation> to #interface PlacemarkAnnotation2 : MKShape. (By the way, for the NSString properties, copy is more appropriate than retain and it'll get rid of warnings.)
It also looks like you may have changed the type of the mkShape ivar in KMLPlacemark (and other places) from MKShape to something else. Change these types back to MKShape.
Next, _createShape might not be the best place to set the placemarkDescription since that method is called for both overlays and annotations. Remove your changes from that method and put them in the point method (also in KMLPlacemark). Note there are a couple of potential memory-related issues with your changes. Here's my suggestion:
- (void)_createShape
{
if (!mkShape) {
mkShape = [[geometry mapkitShape] retain];
mkShape.title = name;
// Skip setting the subtitle for now because they're frequently
// too verbose for viewing on in a callout in most kml files.
}
}
- (id <MKAnnotation>)point
{
[self _createShape];
if ([mkShape isKindOfClass:[PlacemarkAnnotation2 class]])
{
if (placemarkDescription != nil)
//check for nil, otherwise will crash when
//passing to stringByAppendingString below
{
NSString *lessThan = #"<";
NSString *greaterThan = #">";
placemarkDescription = [placemarkDescription stringByReplacingOccurrencesOfString:lessThan
withString:#"<"];
placemarkDescription = [placemarkDescription stringByReplacingOccurrencesOfString:greaterThan
withString:#">"];
NSString *beforeBody = #"<html><body>";
NSString *afterBody = #"</body></html>";
NSString *finalContent = [[beforeBody stringByAppendingString:placemarkDescription]
stringByAppendingString:afterBody];
placemarkDescription = [finalContent retain];
//added retain above since finalContent is autoreleased
//and we are setting the ivar manually. otherwise,
//can result in EXC_BAD_ACCESS later.
}
PlacemarkAnnotation2 *pa2 = (PlacemarkAnnotation2 *)mkShape;
pa2.placemarkDescription = placemarkDescription;
return (id <MKAnnotation>)mkShape;
}
return nil;
}

MKMapView removeAnnotations cause memory leak

everyone!
I have tested this simplest code as following:
StorePin.h
#import <Foundation/Foundation.h>
#import <MAPKIT/mapkit.h>
#import <CORELOCATION/corelocation.h>
#interface StorePin : NSObject <MKAnnotation> {
CLLocationCoordinate2D coordinate;
NSString *subtitle;
NSString *title;
}
#property (nonatomic,assign) CLLocationCoordinate2D coordinate;
#property (nonatomic,retain) NSString *subtitle;
#property (nonatomic,retain) NSString *title;
-(id) initWithCoords:(CLLocationCoordinate2D) coords;
#end
StorePin.m
#import "StorePin.h"
#implementation StorePin
#synthesize coordinate, subtitle, title;
- (id) initWithCoords:(CLLocationCoordinate2D) coords{
self = [super init];
if (self != nil) {
coordinate = coords;
}
return self;
}
- (void) dealloc
{
[title release];
[subtitle release];
[super dealloc];
}
#end
In my ViewControlller, I made a button to add and remove annotations repeatly.
#import "mapViewTestViewController.h"
#import "StorePin.h"
#implementation mapViewTestViewController
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (IBAction)refresh
{
[mapView removeAnnotations:mapView.annotations];
for (int i = 0; i < 101; i ++)
{
CLLocationCoordinate2D p1;
p1.latitude = i/10.0;
p1.longitude = i/10.0;
StorePin *poi = [[StorePin alloc] initWithCoords:p1];
[mapView addAnnotation:poi];
[poi release];
}
}
- (void)dealloc
{
[super dealloc];
}
#end
If I loop less than 100 times to add and remove annotations, all work normally. But if I loop more than 100 times, it will cause memory leak once. I'm nearly crazy to this strange problem. Is this my code's bug or mkmapview's bug? Thank you for helping me.
You don't say what objects have been detected as leaking, but if they are StorePins, then it's MapKit's problem -- your memory management code for the StorePins you create in the loop is just fine.
One thing that you do that might be causing MapKit trouble is passing the map view a reference to its own ivar that you want it to modify. It doesn't seem too likely -- if it was really a problem, it would probably cause a crash rather than a leak. However, you might try making a copy, either shallow (as Kai wrote earlier, but absolutely do not follow the advice about using retain counts and calling release in a loop):
NSArray * annotationsCopy = [NSArray arrayWithArray:mapView.annotations];
or deep:
NSArray * annotationsDeepCopy = [[[NSArray alloc] initWithArray:mapView.annotations
copyItems:YES]
autorelease];
then pass the copy to removeAnnotations:.
The second option creates an autoreleased array with a copy of every item in the annotations list so that the map view doesn't try to remove the same instances that it's iterating over. Obviously this uses twice the memory; you probably only want to bother with this for bug-hunting.
If it fixes the leak, great, if not, then there's probably nothing you can do about it.
In case you don't want to remove the user's location blue dot on the map, you can use:
NSArray * annotationsCopy = [NSArray arrayWithArray:[mapView.annotations filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"!(self isKindOfClass: %#)", [MKUserLocation class]]]];