MKMapView memory usage grows out of control with setRegion: calls - iphone

I have a single MKMapView instance that I have programmatically added to a UIView. As part of the UI, the user can cycle through a list of addresses and the map view is updated to show the correct map for each address as the user goes through them. I create the map view once, and simply change what it displays with setRegion:animated.
The problem is that each time the map is changed to show a new address, the memory usage of my program increases by 200K-500K (as reported by Memory Monitor in Instruments). According to Object Allocations, it appears that a lot of 1.0K Mallocs are happening each time, and the Extended Detail pane for these 1.0K allocations shows that the Responsible Caller is convert_image_data and the Extended Detail pane shows that this is the result of [MKMapTileView drawLayer:inContext:]. So, seems likely to me that the memory usage is due to MKMapView not freeing memory it uses to redraw the map each time. In fact, when I don't display the map at all (by not even adding it as a subview of my main UIView) but still cycle through the addresses (which changes various UILabels and other displayed info) the memory usage for the app does NOT increase. If I add the map view but never update it with setRegion:, the memory also does NOT increase when changing to a new address.
One more bit of info: if I go to a new address (and therefore ask the map to display the new address) the memory jumps as described above. However, if I go back to an address that was already displayed, the memory does not jump when the map redraws with the old address. Also, this happens on iPad (real device) with 3.2 and on iPhone (again, real device) with 3.1.2.
Here's how I initialize the MKMapView (I only do this once):
CGRect mapFrame;
mapFrame.origin.y = 460; // yes, magic numbers. just for testing.
mapFrame.origin.x = 0;
mapFrame.size.height = 500;
mapFrame.size.width = 768;
mapView = [[MKMapView alloc] initWithFrame:mapFrame];
mapView.delegate = self;
[self.view insertSubview:mapView atIndex:0];
And in response to the user selecting an address, I set the map like so:
MKCoordinateRegion region;
MKCoordinateSpan span;
span.latitudeDelta=kStreetMapSpan; // 0.003
span.longitudeDelta=kStreetMapSpan; // 0.003
region.center = address.coords; // coords is CLLocationCoordinate2D
region.span = span;
mapView.region.span = span;
[mapView setRegion:region animated:NO];
Any thoughts? I've scoured the net but haven't seen mention of this problem, and I've reached the limits of my Instruments knowledge. Thanks for any ideas.

What happens when you cycle back through to the original address? Does it still increase?
My thought is that it is likely caching map data, so it will naturally increase as it keeps other locations in memory.

Related

Improve Accuracy of Location Manager in iOS Programming

I am making an application which tracks the user. I have notice when the application goes in the background and then when you open the app it's shows the wrong current location for the user until about 5 seconds. Is there to fix that because that 5 seconds delay ruins the tracking results ( it's adds three extra miles for no reason ).
Edit: The issue wasn't actually a "bug". I have to set in my Info.plist that I want background pocessing and boom the application tracking is super Accurate. A little tutorial to do that:
Go to Info.plist
Add an New Row called "Required background modes"
Then add again a new row called "App registers for location updates"
We are Done :)
One thing you can do is check the horizontalAccuracy property on the CLLocation that you're being returned. If this is above a certain threshold then you could throw away the result and wait for a more accurate one. If it is a number of miles out then I would expect the accuracy figure to be quite large. It's most likely using a cell site to determine location rather than GPS, and the margin of error would be much greater.
In your CLLocationManagerDelegate's locationManager:didUpdateLocations: method you could do the following:
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
if ([locations count] > 0) {
CLLocation *lastUpdatedLocation = [locations lastObject];
CLLocationAccuracy desiredAccuracy = 1000; // 1km accuracy
if (lastUpdatedLocation.horizontalAccuracy > desiredAccuracy) {
// This location is inaccurate. Throw it away and wait for the next call to the delegate.
return;
}
// This is where you do something with your location that's accurate enough.
}
}

json vs xml annotations for mapkit

I have been working on a maps application (iphone) originally I had my annotations set up to pull XML from google using their Places API. I'm having 3 issues.
For my annotation info, I was going
off of an example from Zen
(http://www.zen-sign.com/finding-business-listings-and-displaying-with-mapkit-part-1/
) and he has it set up to do it by
keyword, which wasn't really
necessary for me ( but I used it
anyway just to get a feel for
getting the annotations) in the
parser header he has:
-(void) getBusinessListingsByKeyword:(NSString*)keyword atLat:(float)lat atLng:(float)lng;
and in the the viewdidload of his
view controller
[locationsMap findLocationsByKeyword:#"Apple" ];
I'm not sure how to move from the
keyword parse version used in zen to
something that just does it
automatically (in the parser object- without the viewdidload in a different view controller if possible).
Any advice on what to read/watch or
sample code much appreciated
For places information
Google isn't the only kid on the
block and XML I hear comes in second
to JSON. So I wanted to know what
the best practice was for map
annotations made from business
information: JSON or XML?
The other issue I was having was
only getting 10 annotations (I want
to get 50 or more). So on top of
your advice on using XML or JSON.
How to I increase the amount of
annotations I'm getting.
Sorry for making this 3 parts but again any tutorials (text of video) would be very helpful. (So far I've watched hegarty from Stanford, Larson from MATC)
First
Don't know what you mean by automaticaly. But if you want to launch the map on users current location here you have two methods you can use:
-(IBAction)goToCurrentLocation{
CLLocation *location = [[CLLocation alloc]
initWithLatitude:myMap.userLocation.coordinate.latitude
longitude:myMap.userLocation.coordinate.longitude];
[self setCurrentLocation:location];
}
- (void)setCurrentLocation:(CLLocation *)location {
MKCoordinateRegion region = {{0.0f, 0.0f}, {0.0f, 0.0f}};
region.center = location.coordinate;
region.span.longitudeDelta = 0.15f;
region.span.latitudeDelta = 0.15f;
[self.myMap setRegion:region animated:YES];
}
Second. I don't know what are the best practicies but I used json with this jeson parser for my app ijustmadelove
Three. There is no problem in getting more then 10 annotations on the map. You have to have an error or a limitation in your code.

Going from Google Maps to MapKit on iOS

I am currently using the following code:
UIApplication *app = [UIApplication sharedApplication];
[app openURL:[NSURL URLWithString:#"http://maps.google.com/maps?saddr=Current%20Location&daddr=Chicago"]];
It works perfectly fine for opening up the directions in the google maps App. If I wanted to do this EXACT same thing, only within the App itself, how would I do it? I currently have a viewController set up that has an instance of MKMapView, but it shows the entire world. Everything is working fine but as soon as I try to read the Apple Documentation on Annotations my head starts to spin.
The annotations part is reasonably straightforward once you wrap your head around it, but the path-drawing part is a tremendous hassle. I do it in one of my apps and it took a lot of work to not just draw the lines and keep them scaled and such, but more importantly do it in a speedy and memory-efficient way if the user specifies a route that has literally hundreds of steps (cross-country avoiding highways, for example).
Unless the routing is the focus of your app, I wouldn't bother. If you still really want to I'll go back and review the code and provide some pointers. Just be forewarned that there's a surprisingly lot to it.
I am going to assume that you're doing a lookup for an address and displaying that in your app. You can use the Geocoder API to do a lookup with bounds (or without). I'm using this NSString format: NSString *geocoderURLFormat = #"http://maps.googleapis.com/maps/api/geocode/json?address=%#&bounds=%#&sensor=true".
The callback from this call will return a suggested viewport for the location -- southwest and northeast lat/long values. Using these, you can set the region of the MKMapView object like this:
CLLocationCoordinate2D coord;
coord.latitude = location.latitude;
coord.longitude = location.longitude;
MKCoordinateSpan span;
span.latitudeDelta = location.swLatitude > location.neLatitude ? location.swLatitude - location.neLatitude : location.neLatitude - location.swLatitude;
span.longitudeDelta = location.swLongitude > location.neLongitude ? location.swLongitude - location.neLongitude : location.neLongitude - location.swLongitude;
MKCoordinateRegion region;
region.span = span;
region.center = coord;
[mapView setRegion:region animated:YES];
Note that I'm still a little fuzzy on the latitudeDelta and longitudeDelta values of MKCoordinateSpan, hence the ugliness of the code. And the location variable that I'm using is constructed from the results of the call to Geocoder.
Hope this helps!

How can I group MKAnnotations automatically regarding zoom level?

if the user zooms out on a MKMapView, i want MKAnnotations which are near to each other automatically grouped into one "group" annotation.
if the user zooms back in, the "group" annotation should be split again to the unique/original annotations.
apple does this already in the iOS 4 Photos.app
is there a common, "predefined" way to do this?
Its normal working with more than 1500 annotations on the map:
-(void)mapView:(MKMapView *)mapView_ regionDidChangeAnimated:(BOOL)animated
{
NSMutableSet * coordSet = [[NSMutableSet alloc] init];
for(id<MKAnnotation> an in mapView_.annotations)
{
if([an isKindOfClass:[MKUserLocation class]])
continue;
CGPoint point = [mapView_ convertCoordinate:an.coordinate toPointToView:nil];
CGPoint roundedPoint;
roundedPoint.x = roundf(point.x/10)*10;
roundedPoint.y = roundf(point.y/10)*10;
NSValue * value = [NSValue valueWithCGPoint:roundedPoint];
MKAnnotationView * av = [mapView_ viewForAnnotation:an];
if([coordSet containsObject:value])
{
av.hidden = YES;
}
else
{
[coordSet addObject:value];
av.hidden = NO;
}
}
[coordSet release];
}
That's a brilliant idea. I'm working on a similar app, I hope you don't mind if I als implement the concept :).
To answer your question to the best of my own ability, no, I don't think there is a predefined way to do this.
The best way I can think of to do it (after looking at the iOS4 photos app), is to make use of the mapView:regionDidChangeAnimated: delegate method. Any time the user scrolls/zooms, this method will be called.
Inside that method, you could have some quick geometry math to determine whether your points are "close enough" to consider merging. Once they're "merged", you'd remove one or both annotations, and put another annotation back in the same place that is a reference to both (you could make an AnnotationCluster class very easily that could conform to MKAnnotation but also hold an NSArray of annotations, and also contain methods for "breaking out" or "absorbing" other annotations and/or AnnotationCluster instances, etc).
When I say "quick geometry math", I mean the distance of the two points relative to the span of the map, and taking their relative distance as a percentage of the span of the whole map.
Where that would get tricky is if you had hundreds of annotations, as I can't off-hand think of a good way to implement that w/o a double loop.
What do you reckon?
This project does something interesting. Though, have a look at reported issues before changing too many things in your code. Because it could be not good enough for your needs yet.
I personnaly ended up implementing this

How to simulate a user driving a route in a MKMapView?

I need to simulate how my application will look when a user is driving around for a demo. I have a MKMapView, how can I simulate the look of a user driving around which will use the map.userLocation functionality, which obviously will not be available in the demo.
Thanks!
No way to simulate in iPhone simulator. You'll need to load it onto your device and move around.
Well I got something going, I just did essentially this
- (void)moveIcon:(MKAnnotationView*)locationView toLocation:(CLLocation*)newLoc
{
LocationAnnotation* annotation = [[[LocationAnnotation alloc] initWithCoordinate:newLoc.coordinate] autorelease];
[locationView setAnnotation:annotation];
[map setCenterCoordinate:newLoc.coordinate animated:YES];
}
Then I call this guy in a loop between all of my vertices with a slight delay. Works quite qell.
I'm not an iPhone dev expert, but how does the map view receive the coordinates? If it's through a function that calls the CoreLocation API, could you possibly just write a function that randomly generates longitude and latitude values at a certain time interval and have your map view pull the coordinates from there instead? Just a thought.
You could also check out iSimulate which claims to be able to simulate several features only available on the iPhone in the iPhone simulator include CoreLocation. I have not tried this myself so your mileage may vary.
In order to simulate driving you'll need to establish 2 basic functionalities:
Reading CLLocations from an archive (which you'd log during the drive test with a device). Ideally you'll do this based on the timestamps on the locations, i.e. reproducing the exact same location updates which were received during the drive test.
Updating your MKAnnotationView's position on the map based on the locations read from log.
For part 1, take a look at CLLocationDispatch, a handy class which provides archiving/unarchiving of CLLocations and dispatches them to one or more listeners (using CLLocationManagerDelegate protocol).
For part 2, take a look at Moving-MKAnnotationView.
I found a better way would be to subclass MKUserLocation:
class SimulatedUserLocation: MKUserLocation {
private var simulatedCoordinate = CLLocationCoordinate2D(latitude: 39, longitude: -76)
override dynamic var coordinate: CLLocationCoordinate2D {
get {
return simulatedCoordinate
}
set {
simulatedCoordinate = newValue
}
}
}
Then add it as an annotation mapView.addAnnotation(SimulatedUserLocation()). (You might also want to hide the real location first mapView.showsUserLocation = false)
iOS would render the annotation exactly like the real user location.
dynamic is used on the property so that changing coordinate triggers KVO and moves it on the map.
The answer is NO. Then, how about adding an abstraction layer between your code and MKMapKit? You can do xUnit tests for your objective.