MKMapView Memory Leak in iPhone Application - iphone

I am working on an iPhone application which uses MKMapView and shows userlocation. I am getting memory leaks where leaked object is NSCFArray of size 128 Bytes, GeneralBlock-16, GenralBlock-8 when is set MKMapView's showUserLocation property as TRUE. If is set it as NO then i dont get this leak. Can anyone suggest that what can be the possible reason for this. Is this a bug in MKMapView class or is am I using the MKMapView incorrectly. Can someone tell me what is the best way to use MKMapView and show userLocation also.
Thanks & Regards,
Priyanka Aggarwal

I have the exact same issue. It looks like a bug in MKMapView to me. Three workarounds come to mind:
Create your own annotation for the current position.
Don't destroy and recreate the view so you only get the leak once.
Turn it off.
Fortunately for me I can turn it off without any significant loss of feature.

I fixed a similar issue by autoreleasing my annotationView objects. Also, MKUserLocation is an annotation object, so checking for your own annotation objects (or checking to see if the annotation object is MKUserLocation), and returning nil for other annotation objects (or MKUserLocation) will tell map kit to use the default MKUserLocation object. Putting these checks into place could stop your leak. See below:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
{ static NSString *placemarkIdentifier = #"placemark_identifier";
if ([annotation isKindOfClass:[MyPlaceMark class]]) {
MKAnnotationView *annotationView = [mapView dequeueReusableAnnotationViewWithIdentifier:placemarkIdentifier];
if (annotationView == nil) {
annotationView = [[[MyPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:placemarkIdentifier] autorelease];
} else {
annotationView.annotation = annotation;
}
return annotationView;
}
return nil;
}
MKUserLocation class reference

For what its worth, there are similar related questions here:
https://stackoverflow.com/questions/5935243/mkmapview-rame-et-fuite-memoire-apple
Is MKMapView leaky
Can the memory used by MKMapView be released some how?

Related

mapView:viewForAnnotation: not called when delegate set in code

I have a custom class for my MKAnnotations, I want to override the default mapView:viewForAnnotatation method so that I can add extra information in the callout. When I set my delegate in code (as per the code below) the annotations are dropped on the map and are selectable but my mapView:viewForAnnoation is never called.
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation{
NSLog(#"viewForAnnotation: called");
MKAnnotationView *aView = [mapView dequeueReusableAnnotationViewWithIdentifier:#"mapPin"];
if(!aView){
aView = [[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"mapPin"];
}
aView.annotation = annotation;
return aView;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
self.mapView.delegate = self;
}
I know the delegate is being set as I can override the method -(void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view and I see an NSLog when I select an annotation.
When I change from setting the delegate in code to setting it in the Storyboard the method is called (NSLog(#"viewForAnnotation: called"); statements appear) but the annotations don't appear on the map and sometimes this error appears:
<Error>: ImageIO: CGImageReadSessionGetCachedImageBlockData *** CGImageReadSessionGetCachedImageBlockData: bad readSession [0x8618480]
This seems like two separate issues:
Regarding the setting delegate in code v storyboard, it's hard to reconcile your various observations (delegate method didSelectAnnotationView is getting called in both scenarios, but viewForAnnotation is not). The only difference between setting it in code v in the storyboard is the timing of when the delegate is getting set. You're not showing us the process of adding the annotations, so it's hard to diagnose on the basis of what you've described. If none of your delegate methods were getting called, I'd suspect the mapView IBOutlet, but if some are working and others aren't, I can only suspect a timing issue.
Regarding the setting of the MKAnnotationView, the default implementation does nothing. You either need to write your own subclass of MKAnnotationView, set its image if you're using your own image, or, much easier, just use MKPinAnnotationView. But just creating a MKAnnotationView won't do anything. You really want:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation{
// If it's the user location, just return nil.
if ([annotation isKindOfClass:[MKUserLocation class]])
return nil;
// Handle any custom annotations.
MKAnnotationView *aView = [mapView dequeueReusableAnnotationViewWithIdentifier:#"mapPin"];
if(aView){
aView.annotation = annotation;
} else {
aView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"mapPin"];
}
aView.canShowCallout = NO;
return aView;
}
(Note, I'm not only creating a MKPinAnnotationView, but I'm also making sure that it's not a MKUserLocation, in case you ever choose to show user location on the map. I'm also going to explicitly set the canShowCallout, as that's presumably the reason you're writing this method at all.)
Bottom line, if you want to show a simple pin annotation view, use MKPinAnnotationView. Using MKAnnotationView alone will result in no annotations from appearing.
In case someone else is searching for a reason why mapView:viewForAnnotatation is not being called when the delegate is set in code, there is a bug in iOS 6 - http://openradar.appspot.com/12346693
I came across the same issue and I wanted to share my solution:
I also override
(MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation
But I realised that these Annotations work similar to TableView and iOS will reuse the annotations like the cells in TVC (table view controller)
Since I was only using one Identifier mapView dequeueReusableAnnotationViewWithIdentifier:#"mapPin" it would not need to call ViewForAnnotation again if it had enough 'annotation' in memory.
So my solution was to create multiple Identifier the first time the map load based on my conditions.
This solved my problem.
i just dropped to this question accidentally when i was searching for the same problem.
I solved my problem, in my case im resizing the mapview.
I added delegate after i resize the mapview. it works now perfectly.!

IPhone - Removing the 'currentLocation' blue dot from an MKMapView

I'm trying to replace the blue 'currentLocation' dot with a custom annotation. That code is working well (I am just implementing viewForAnnotation and cycling through that, replacing the annotation ofClass MKUserLocation with a custom image).
However, once I replace the annotation, the user's current location stops updating. All related functions (like didUpdateUserLocation) thus stop being called. This causes a lot of problems. I tried implementing the various code blocks at MKUserLocation Custom View not moving! but I couldn't get it to work. After extensive Googling and stackoverflow searching, I've come up with nothing.
Does anyone have a solution to this problem?
Here is the code for the viewForAnnotation method:
_userDot is an instance variable within MapScreen.m (the place where all of this code is). It's alloced in the viewDidLoad and is of type MKAnnotationView. Basically I couldn't just get rid of the annotation, so I wanted to set it to an invisible image instead (currently 1x1 for debugging).
- (MKAnnotationView *)mapView:(MKMapView *)newMapView viewForAnnotation:(id <MKAnnotation>)annotation
{
if ([annotation isKindOfClass:[MKUserLocation class]]) {
//Initialize a new MKAnnotationView using the current userLocation annotation
//Then make it invisible (because we are cheap like that)
//TODO: Make it actually invisible (right now it is 1x1 for debugging)
if (_userDot)
{
_userDot.annotation = annotation;
_userDot.image = [UIImage imageNamed:#"mister_taco"];
_userDot.frame = CGRectMake(1, 1, 1, 1);
return _userDot;
}
else
return nil;
}
else {*/
return nil;
//}
}
Thanks,
Brian

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.

Problem with selected annotations

:)
I have a really strange problem when trying to retrieve the properties of a selected annotation. Here is the short description of my problem:
I pass the first object of the selected annotations array to a new array as it's the only one I need (acc. to Apple doc, passing the selectedAnnotations array to a new array only selects the first object. But I did tried to pull the object directly from the selectedAnnotations array at index path 0 and it's the same problem).
Then I transform the object into a Custom annotation object (as this is what the object should be).
Afterwards I try to access the properties of my custom annotation temp object. Here is when everything breaks loose. NSLog of the object only shows memory address. Text property is null. So basically I can't access it.
I would appreciate any help on what am I doing wrong or what approach should I use.
Thank you kindly!
Here is the code:
-(void) mapView:(MKMapView *)aMapView didSelectAnnotationView:(MKAnnotationView *)view
{
if ([view isUserInteractionEnabled])
// NSLog(#"Tapped!!!");
{
NSArray* selectedAnnotation=mapView.selectedAnnotations;
CustomAnnotations *selectedAnn=[selectedAnnotation objectAtIndex:0];
NSLog(#"selected annotation text is %#", selectedAnn.text);
My custom annotation class has a coordinate and a text property and it's being placed on map with this code:
CustomAnnotations* commentAnnotation = [[[CustomAnnotations alloc] initWithLocation:mapView.userLocation.location.coordinate andTitle:#"comment" andText:text]autorelease];
[mapView addAnnotation:commentAnnotation];
Furthermore, the view for annotation has the following coding:
-(MKAnnotationView *) mapView:(MKMapView *) aMapView viewForAnnotation:(id<MKAnnotation>)annotation
{
if ([annotation isKindOfClass:[MKUserLocation class]])
return nil;
MKAnnotationView *customAnnotation = (MKAnnotationView*)[mapView dequeueReusableAnnotationViewWithIdentifier:#"pin"];
if(!customAnnotation)
{
customAnnotation = [[[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"pin"]autorelease];
}
customAnnotation.centerOffset=CGPointMake(10, -30);
if ([annotation title]==#"comment")
{
customAnnotation.userInteractionEnabled=YES;
customAnnotation.image=[UIImage imageNamed:#"NewCommentsPin.png"];
}
return customAnnotation;
}
Any help would be much appreciated!
I figured out the problem: My custom annotation class was releasing the text in dealloc.
I still have a long way to go until I understand when to release and when not to but one step at a time!:)
Here is your release cheat sheet.
When you create an object, and it has any of the words new, alloc,
copy or retain in the constructor then you own it and you have
to release it if you do not need the reference any more.

Change MKMapType in MKMapView and keep custom pinImage for annotations

I have set a custom pinImage for my annotations and when I change the type toMKMapTypeHybrid it reverts the pinImage setting to the standard pins.
I'm setting the mapType in my viewWillAppear method of the map view controller. I'm setting my pinImage for the annotations like so (shortened for clarity):
- (MKAnnotationView *) mapView:(MKMapView *) mapView viewForAnnotation:(id ) annotation {
MKPinAnnotationView *customAnnotationView=[[[MKPinAnnotationView alloc]
initWithAnnotation:annotation
reuseIdentifier:#"markerAnnotationView"] autorelease];
UIImage *pinImage = [UIImage imageNamed:#"/pin-image"];
[customAnnotationView setImage:pinImage];
return customAnnotationView;
}
Is there any way to use setImage and set the mapType via code?
The solution to this problem was to use an MKAnnotationView instead of the MKPinAnnotationView. I had previously been using the MKPinAnnotationView for good reason but later added a custom image to the pin without changing the instantiating class to MKAnnotationView.
Now, I think it is probably buggy framework behavior. If you're going to let an MKPinAnnotationView accept a setImage call then make sure that it handles it correctly when you change mapTypes. If you only want them to do pins then don't do anything with the call. Ah well.