Problem adding multiple annotations to map - iphone

Ok, so I’m having this problem. What I want to do is manually add multiple annotations to a map. When I add just one annotation, it works flawlessly. The pin drops, you can click on it to see its callout, life is good.
The problem comes when I want to add more than one. When I add the second, suddenly the pin’s aren’t coloured correctly (i.e. depending on their magnitude they should be a certain color, but they’re now both the same…), and more importantly when you click on them, to see their callout, the app crashes with exex_bad_access. I really have no idea what’s wrong, maybe I’m adding too many views to the map? But it’s only 9 pins and the pins themselves add just fine.
Here’s my code…
- (void)viewDidLoad {
[super viewDidLoad];
NSMutableArray *stops = [[NSMutableArray alloc] init]; //Get list of all the stops available
Bus *bus1 = [[Bus alloc] init]; // Bus 1 holds the stops
stops = [bus1 returnStops];
for (NSString *stop in stops) //Go through each stop to add annotation to map
{
Bus *bus2 = [bus1 initWithStop:stop]; //Create an instance of bus with a given stop
MapAnnotation *eqAnn = [MapAnnotation annotationWithBus:bus2];
[self.mapView addAnnotation:eqAnn]; //Add the annotation to the map
//[eqAnn release];
//[bus2 release];
}
[self recenterMap];
[stops release];
}
- (MKAnnotationView *)mapView:(MKMapView *)mapView
viewForAnnotation:(id <MKAnnotation>)annotation {
MKAnnotationView *view = nil;
if(annotation != mapView.userLocation) {
MapAnnotation *eqAnn = (MapAnnotation*)annotation;
view = [self.mapView dequeueReusableAnnotationViewWithIdentifier:#"busLoc"];
if(nil == view) {
view = [[[MKPinAnnotationView alloc] initWithAnnotation:eqAnn
reuseIdentifier:#"busLoc"] autorelease];
}
CGFloat magnituide = [eqAnn.bus.magnitude floatValue];
if(magnituide >= .80f) {
[(MKPinAnnotationView *)view setPinColor:MKPinAnnotationColorRed];
} else if(magnituide >= .60f) {
[(MKPinAnnotationView *)view setPinColor:MKPinAnnotationColorPurple];
} else
{
[(MKPinAnnotationView *)view setPinColor:MKPinAnnotationColorGreen];
}
[(MKPinAnnotationView *)view setAnimatesDrop:YES];
[view setCanShowCallout:YES];
}
return view;
}
even tried removing the second function, but it didn’t do anything.
Thanks for the help!
P.S I should also add, there’s usually one or two pins out of the 9 which works when you click the annotation…
If i even try to manually just two annotations by hand in the program (i.e., remove the loop), it still fails and the color is still wrong...

It would appear that your memory management of the stops variable is incorrect. You allocate a mutable array, then replace that array with the return value of -[Bus returnStops], then release that. Also it's not clear what's going on with bus2 - does -[Bus initWithStop:] return a different instance of Bus? It's not usual to send any method -init* on an already-initialised object. I think that you probably are confused by the memory management conventions in Cocoa Touch. Here's a collection of articles and other references on Cocoa memory management (which is the same beast).

Have you tried using AddAnnotations instead of add annotation? - (void)addAnnotations:(NSArray *)annotations. This might work for you...but looking at the answer above and further inspection you are having some memory managment issues in your viewDidLoad (though thi s might not be the cause of your problem, but it could be). First of you are allocating the array (stops) and then ovveriding it with some array in the Bus object, this will cause a leak. Also you are then releasing that array which might be causing the crash since you are releasing the array that is actually in the Bus object w ithout having increased a reference count to it. I am not sure what initWithStop is doing but you might be getting a leak here too if initWithStop retains the object.

I wouldn't call it a memory management problem -- I'd just say you are using array references incorrectly.
After constructing the array with NSMutableArray *stops = [[NSMutableArray alloc] init], the next step is to use [stops addObject: ] to add each stop you want to store.
After that? It's not clear what you are really trying to do.

SO the answer was that I kept sending bus1 the init object, so it got confused.
"Hi David,
Your data model looks hosed to me. You only have one bus object that you are repeatedly sending initWithStop: to.
Hope this helps.
Good luck!
"
Thank you guys for your help! You all helped me quite a bit!

Related

How to manage a huge amount of MapViewAnnotations

I have a MapView with a huge amount of annotations (8000+). The performance is going down by scrolling the map while all annotations are visible. So, I had the idea to hide some annotations when the are in the visible area. I found this thread in the forum, but it doesn't solve my problem.
I tried another solution by writing this piece of code
- (void)mapView:(MKMapView *)_mapView regionDidChangeAnimated:(BOOL)animated
{
NSSet *annoSet = [[NSSet alloc] initWithSet:[_mapView annotationsInMapRect:_mapView.visibleMapRect]];
NSMutableSet *annotationsSet = (NSMutableSet *)annoSet;
NSLog(#"Annos: %i", [annotationsSet count]);
if([annotationsSet count]>500)
{
for(MapViewAnnotation* annotation in annotationsSet)
{
[[_mapView viewForAnnotation:annotation] setHidden:YES];
}
}
}
This works, but after that operation the mapView is near a total breakdown, what can I improve?
Edit:
My new code looks like this:
- (void)mapView:(MKMapView *)_mapView regionDidChangeAnimated:(BOOL)animated
{
NSSet *annoSet = [[NSSet alloc] initWithSet:[_mapView annotationsInMapRect:_mapView.visibleMapRect]];
//NSMutableSet *annotationsSet = (NSMutableSet *)annoSet;
NSLog(#"Annos: %i", [annoSet count]);
if([annoSet count]>500)
{
for(MapViewAnnotation* annotation in annoSet)
{
[[_mapView viewForAnnotation:annotation] setHidden:YES];
}
}
else if([annoSet count] <= 500)
{
for(MapViewAnnotation* annotation in annoSet)
{
[[_mapView viewForAnnotation:annotation] setHidden:NO];
}
}
}
but i dont want to hide all annotations in the visibleRect just a few of them.
Any idea how i can do that in an efficient way?
What you're looking for is a way to cluster your annotations, which means to display a single annotation where there are several annotations in close proximity. This can get somewhat complicated if you want to do it right, but if you search the Net, you will find several places that discuss clustering strategies and algorithms. I've linked to a number of them below.
This blog post on the Applidium website discusses one such strategy, and there are now several projects on Github (mapkit-clusters, kingpin, RevClusterMap) that can take care of clustering for you. If nothing else, take a look at the code and you'll get some idea of how to deal with the problem.
Back when my team was working on the problem, however, we went with a solution described in one of the WWDC videos (this one, I think (developer account required)), which we modified somewhat to improve performance.
Note that you will probably have to find a way to let the user know when they click on a clustered annotation. We did this with a custom callout that listed all the annotations represented by that single annotation.

iOS rotating MKAnnotationView in response of MKMapView rotation

In my application I have a MKMapView where several annotations are shown. The map rotates based on the heading of the device. To rotate the map the following statement is performed (called by the method locationManager: didUpdateHeading:)
self.navigationMapView.mapView.transform = CGAffineTransformMakeRotation(-heading);
where the heading (magnetic) is expressed in radians. What I noticed it's that even the annotations in the map rotate and I don't want it. I tried to fix it in the following method:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation{
static NSString *identifier = #"AnnotationViewIdentifier";
MKAnnotationView *av = [mapView dequeueReusableAnnotationViewWithIdentifier:identifier];
if (av == nil) {
av = [[[MKPinAnnotationView alloc]initWithAnnotation:annotation reuseIdentifier:identifier] autorelease];
}
else{
av.annotation = annotation;
}
av.transform = CGAffineTransformMakeRotation(degreesToRadians(self.arController.currentHeading.magneticHeading));
av.canShowCallout = YES;
return av;
}
and I want to call this method from "didUpdateHeading:" but I really don't know how to do it. The TableView class has the reloadData function that calls the delegate method but here the things seem different. Any suggestions?!
Another question, my annotations on the map show the distance from the user, I would like to update them (distance label) as soon as the user change location. Any suggestions?!
So with a MKMapView having that be called properly is a little bit annoying. Essentially you have one of two options. Option 1: Create an array of the annotation on the screen and remove that from the map_view and then re-add them to the map_view. Essentially creating your own reload data function. Option 2: Do something simple such as
CGLocationCoordinate2D coordinate = map_view.center;
map_view.center = coordinate;
-- Essentially the point is to reset a property of the map causing it to redraw. However this option is not always going to work. Option 1 has a higher chance of working however that one can also fail, so if simply taking the annotations off and re-adding them causes nothing to happen then simply decreate the map and then recreate the map at the end of your map refresh function something like.
[my_map_view removeFromSuperView];
[my_map_view release];
my_map_view = nil;
my_map_view = [[MKMapView alloc] initWithFrame:CGRectMake(0,0,320,480)];
one of these options should work. I had to do option one for my solution however I know some people are lucky and option 2 works just as well.

Iphone application. Crash without the self keyword

I will try to make myself as clear as possible. Let start from the beginning. I have an application with a tableview that contains a list of places with distances from myLocation. Now everytime I get an update in the gps location I run the following code
- (void)locationUpdate:(CLLocation *)location {
myLocation = location;
for (Trek * trek in list) {
CLLocation *loc = [[CLLocation alloc] initWithLatitude:[trek latitude_start] longitude:[trek longitude_start]];
double dis = [locationManager getDistance: loc];
[trek setDistance:dis];
[trek setDistanceUnit];
[loc release];
}
[self.tableView reloadData];
}
Now this piece of code [trek setDistanceUnit]; calls
-(void) setDistanceUnit {
if (self.distance < 1000.0)
self.distanceString = [NSString stringWithFormat:#"%.0lf m", self.distance];
}
Now if I use only distanceString the application crash. Now I think it may have something to do with the fact that those updates may run concurrently (in parallel) to the access required by the view to draw the cells. Anyone has any idea? I can post more code if helpful, I just didn't want to post too much to make this post too long.
I tried to search everywhere but I could not found anything so far.
Thanks in advance,
Umberto
PS Now the application is working but I would like to understand what is going on.
If your distanceString is a retain property, assigning it without self sets it up for a crash because you bypass the setter, and assign the string without retaining it. So when the string gets deallocated on being sent to the autorelease pool, your app crashes.
By synthesizing the accessors using #synthesize and using the dot notation (or setDistanceString:), the object will retain the string for you so that it always has a pointer to it for itself (until it's released).

EXC_BAD_ACCESS when setting ivars directly (without using accessors) inside -init method, why?

I've spent about 10 hours trying to find this bug that was causing my app to crash, and it was in the last place I looked (well it would have been, but last place I ever expected it to be).
Originally I thought I had memory management issues (unbalanced retain/release) because the crash would happen every time I sent -removeAllObjects to an NSMutableArray filled with my custom objects. The crash wouldn't happen the first time -removeAllObjects was called. I could clear the array once, repopulate it, and then on the second clear, I would get a EXC_BAD_ACCESS. This is when my array got populated with 3 objects on the first "cycle", and 3 again on the second "cycle". When I was storing only 1 object in the array in each cycle it took 4 cycles to crash (on the 4th call of -removeAllObjects).
I FINALLY realised that the crash would go away if I changed the -init method of my custom object. Here's the -init implementation; all 4 ivars are synthesized properties with (nonatomic, retain), all of type (NSString *) except for icon which is an (NSNUmber *)
-(id)init {
if (self = [super init]) {
ip = #"";
mac = #"";
vendor = #"";
icon = [NSNumber numberWithInt:0];
}
return self;
}
Changing it to this fixed the bug:
-(id)init {
if (self = [super init]) {
self.ip = #"";
self.mac = #"";
self.vendor = #"";
self.icon = [NSNumber numberWithInt:0];
}
return self;
}
I've read that one should't use the accessors in the -init method because it can cause trouble (e.g. with subclassing).
If someone could explain to me why my bug goes away when I use accessors I would be so incredibly grateful! Seriously this has been driving me nuts, was up till 5am last night because of this.
You are assigning, but not retaining, the instance variables directly. When you use the dot syntax, you are triggering the retain part of the synthesized property and, thus, are retaining them.
-(id)init {
if (self = [super init]) {
ip = #"";
mac = #"";
vendor = #"";
icon = [[NSNumber numberWithInt:0] retain];
}
return self;
}
That should likely fix the problem (though, I'm slightly surprised, I thought 10 was still in NSNumber's instance cache. Maybe not.).
Technically you should also retain the #"" strings, but you can get away with not doing so because such strings are a special cased constant string that comes out of the compiled executable (as a private subclass of NSString that overrides to not respond to retain/release/autorelease).
The memory management guide covers this in detail. For anyone new to the platform, I would suggest re-reading it once a month (no, really -- interleaving your coding with an occasional re-read of the docs will often reveal subtle details that you didn't have enough experience to grok before. I still re-read the basic guides on a semi-annual basis.)

Trying to get MKPolygon overlay working

Trying to figure out this MKPolygon working. I've created a whole heap MKMapPoints and placed them into an array. Then I make a Polygon out of them with:
[MKPolygon polygonWithPoints:pointArr count:sqlite3_column_int(countStatement, 0)];
and add that to an Array for retrieval later.
Later I loop through the array and add each object (MKPolygon) to the map with:
[mapView addOverlay:[overlays objectAtIndex:i]];
And this according to logs works fine.
I then implement mapView: viewForOverlay: like so
- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id <MKOverlay>)overlay {
MKOverlayView *theOverlayView = nil;
for (MKPolygon *theOverlay in overlays) {
if (theOverlay == overlay) {
MKPolygonView *thePolygonView = [[[MKPolygonView alloc] initWithPolygon:theOverlay] autorelease];
theOverlayView = thePolygonView;
}
}
return theOverlayView;
}
As far as I can see this should technically work. But it doesn't, the App just crashes when it gets to the region of the map that I think its on (points may be wrong because of lat long mixup or conversion mistake but thats another thing)
Am I missing some property I need to set or could the incorrect points be causing the crash? Or am I missing it entirely?
Cheers for any help.
This was actually correct but I made a mistake putting more than I remembered into overlays - it was actually and array of dictionaries which stored the MKPolygon and a string. Correcting this got it working.