Clear MKSnapshotter memory - ios

Im currently using the MKSnapshotter but I noticed (similar to MKMapView) that it holds on to a high memory consumption and never releases it for the duration of the app. I've tried releasing the memory but no use:
-(void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
[self releaseMKMapSnapshotMem];
}
-(void)releaseMKMapSnapshotMem{
self.snapshotter=nil;//MKSnapShotter
self.options=nil;//MKSnapShotterOptions
}
Any help is greatly appreciated.
Update
Includes more detail
MKMapSnapshotOptions * snapOptions= [[MKMapSnapshotOptions alloc] init];
self.options=snapOptions;
CLLocation * salonLocation = [[CLLocation alloc] initWithLatitude:self.lat longitude:self.long];
MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(location.coordinate, 300, 300);
self.options.region = region;
self.options.size = self.view.frame.size;
self.options.scale = [[UIScreen mainScreen] scale];
MKMapSnapshotter * mapSnapShot = [[MKMapSnapshotter alloc] initWithOptions:self.options];
self.snapshotter =mapSnapShot;
[self.snapshotter startWithCompletionHandler:^(MKMapSnapshot *snapshot, NSError *error) {
if (error) {
NSLog(#"[Error] %#", error);
return;
}
UIImage *image = snapshot.image;
self.mapImage = image;
NSData *data = UIImagePNGRepresentation(image);
[self saveMapDataToCache:data WithKey:mapName];
}];

Try this:
MKMapSnapshotOptions * snapOptions= [[MKMapSnapshotOptions alloc] init];
CLLocation * salonLocation = [[CLLocation alloc] initWithLatitude:self.lat longitude:self.long];
//location.coordinate or salonLocation.coordinate below????
MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(location.coordinate, 300, 300);
snapOptions.region = region;
snapOptions.size = self.view.frame.size;
snapOptions.scale = [[UIScreen mainScreen] scale];
MKMapSnapshotter * mapSnapShot = [[MKMapSnapshotter alloc] initWithOptions: snapOptions];
[mapSnapShot startWithCompletionHandler:^(MKMapSnapshot *snapshot, NSError *error) {
if (error) {
NSLog(#"[Error] %#", error);
return;
}
UIImage *image = snapshot.image;
NSData *data = UIImagePNGRepresentation(image);
[self saveMapDataToCache:data WithKey:mapName];
}];
I'm doing pretty much the same as you're doing with only two differences:
options.region = _mapView.region;
options.size = _mapView.frame.size;
my _mapView is the map being displayed in the view controller...

Related

Objective C Loading Icon on Timer

I have created a loading icon for my application. While the application is loading the map and placing the markers I have a loading icon displaying on the screen rotating. With my current code the loading icon shows, but only rotates when the markers are all placed on the map and everything is finished loading. I have tried about everything, can anyone help?
I will attach code below, I do understand this is not the best way to do it and I plan to neaten it up once I find out what I am doing wrong getting it to rotate when the map is loading.
Thanks.
- (void)viewDidLoad
{
[super viewDidLoad];
loading=#"0";
rotateTimer = [NSTimer scheduledTimerWithTimeInterval:0.3
target:self
selector:#selector(rotateMove)
userInfo:nil
repeats:YES];
}
-(void)rotateMove
{
if([loading isEqual:#"1"])
{
[rotateTimer invalidate];
rotateTimer = nil;
}
if([loading isEqual:#"0"])
{
NSLog(#"move");
[UIView animateWithDuration:1.0f delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
[self.rotate setTransform:CGAffineTransformRotate(self.rotate.transform, M_PI_2)];
} completion:^(BOOL finished){
if (finished) {
}
}];
}
}
EDITED: ADDED MAP CODE BELOW
-(void)mapload
{
[self.mapView clear];
NSString *lat = [[NSString alloc] initWithFormat:#"%g", latitude];
NSString *lng = [[NSString alloc] initWithFormat:#"%g", longitude];
NSURL *blogURL =
NSLog(#"URL = %#", blogURL);
NSData *jsonData = [NSData dataWithContentsOfURL:blogURL];
if(jsonData == nil)
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Connection Error" message:#"Please check your internet connection and try again." delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alert show];
return;
}
else{
NSError *error = nil;
NSDictionary *dataDictionary = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error];
NSArray *test = [dataDictionary objectForKey:#"test"];
self.bottombutton.hidden=FALSE;
self.time.hidden=FALSE;
self.mins.hidden=FALSE;
self.rotate.hidden=TRUE;
int tes = [[test[0] valueForKey:#"time"] intValue];
}
for (int i = 0; i < [test count]; i++) {
for(NSDictionary *coordinates in test){
double la=[coordinates[#"lat"] doubleValue];
double lo=[coordinates[#"long"] doubleValue];
CLLocation * loca=[[CLLocation alloc]initWithLatitude:la longitude:lo];
CLLocationCoordinate2D coordi=loca.coordinate;
GMSMarker *marker= [[GMSMarker alloc] init];
marker=[GMSMarker markerWithPosition:coordi];
marker.snippet = coordinates[#"name"];
marker.map = self.mapView;
marker.appearAnimation = kGMSMarkerAnimationPop;
UIImage * image = [UIImage imageNamed:#"mapiconlarge"];
CGSize sacleSize = CGSizeMake(45, 45);
UIGraphicsBeginImageContextWithOptions(sacleSize, NO, 0.0);
[image drawInRect:CGRectMake(0, 0, sacleSize.width, sacleSize.height)];
UIImage * resizedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
NSLog(#"markeradded");
marker.icon = resizedImage;
NSLog(loading);
}
}
}
}
We need to tease apart the part of your code that should be done in the background -- at the very least, the network request -- and the code that must be done on the main -- anything that changes the UI...
// here, everything you do to prepare for the network request
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// make the network request
NSData *jsonData = [NSData dataWithContentsOfURL:blogURL];
dispatch_async(dispatch_get_main_queue(), ^{
// here everything you do after with the json data result
});
});
So you don't drive yourself crazy with syntax, build two methods, the first one produces blogURL for use on the network request, the second, takes the NSData result and does everything else. This way, there's just a one-liner method invocation nested in that inner dispatch.

handleWatchKitExtensionRequest+ asynchronous call

I am trying to use MKMapSnapshotter to take a snapshot from the Map on the containing iPhone app, and then send the image back to the watch. When the containing iPhone app is not running on the background, I cannot get it to work.
This is how I am trying to do:
on AppDelegate:
- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void ( ^)( NSDictionary * ))reply {
__block UIBackgroundTaskIdentifier watchKitHandler;
watchKitHandler = [[UIApplication sharedApplication]
beginBackgroundTaskWithName:#"backgroundTask" expirationHandler:^{
watchKitHandler = UIBackgroundTaskInvalid;
}];
NSMutableDictionary *response = [NSMutableDictionary dictionary];
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
//========================Building the snapshot===========
CLLocationDegrees latitude = 37.331793f;
CLLocationDegrees longitude = -122.029584f;
MKMapView* mapView = [[MKMapView alloc] initWithFrame:CGRectMake(0, 0, (156*2), (108*2))];
mapView.mapType = MKMapTypeHybrid;
CLLocationCoordinate2D coord = CLLocationCoordinate2DMake(latitude, longitude);
MKCoordinateSpan span = {.latitudeDelta = 0.01, .longitudeDelta = 0.01};
MKCoordinateRegion region = {coord, span};
[mapView setRegion:region];
MKMapSnapshotOptions *options = [[MKMapSnapshotOptions alloc] init];
options.region = mapView.region;
options.size = mapView.frame.size;
options.scale = [[UIScreen mainScreen] scale];
MKMapSnapshotter *snapshotter = [[MKMapSnapshotter alloc] initWithOptions:options];
//========================================================
[snapshotter startWithCompletionHandler:^(MKMapSnapshot *snapshot, NSError *error) {
NSData *imageData = UIImagePNGRepresentation(snapshot.image);
[response setObject:imageData forKey:#"snapshotimage"];
reply(response);
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
dispatch_after(dispatch_time( DISPATCH_TIME_NOW, (int64_t)NSEC_PER_SEC * 1), dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[[UIApplication sharedApplication] endBackgroundTask:watchKitHandler];
});
}
I am trying different codes suggested by people but I should be doing something wrong in here. Please notice that the "snapshotter startWithCompletionHandler" is done asynchronously.
I have tried the following suggestions but I should be missing something here.
https://stackoverflow.com/a/30000323/4982919
Any comment and help is greatly appreciated.

MKMapSnapshotter with MKPolylineRenderer problems

I tried implementing this answer: https://stackoverflow.com/a/22716610, to the problem of adding overlays to mkmapsnapshotter in iOS7 (cant do renderInContext method). I did this as shown below, but the image returned has only the map with no overlays. Forgive me, I am quite new to this. Thanks.
-(void)mapViewDidFinishRenderingMap:(MKMapView *)mapView fullyRendered:(BOOL)fullyRendered
{
if (mapView.tag == 100) {
MKMapSnapshotOptions *options = [[MKMapSnapshotOptions alloc] init];
options.region = mapView.region;
options.size = mapView.frame.size;
options.scale = [[UIScreen mainScreen] scale];
MKMapSnapshotter *snapshotter = [[MKMapSnapshotter alloc] initWithOptions:options];
[snapshotter startWithCompletionHandler:^(MKMapSnapshot *snapshot, NSError *error) {
if (error) {
NSLog(#"[Error] %#", error);
return;
}
UIImage *image = snapshot.image;
UIGraphicsBeginImageContextWithOptions(image.size, YES, image.scale);
{
[image drawAtPoint:CGPointMake(0, 0)];
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);
CGContextSetLineWidth(context,5.0f);
CGContextBeginPath(context);
bool first = YES;
NSArray *overlays = mapView.overlays;
for (id <MKOverlay> overlay in overlays) {
CGPoint point = [snapshot pointForCoordinate:overlay.coordinate];
if(first)
{
first = NO;
CGContextMoveToPoint(context,point.x, point.y);
}
else{
CGContextAddLineToPoint(context,point.x, point.y);
}
}
UIImage *compositeImage = UIGraphicsGetImageFromCurrentImageContext();
NSData *data = UIImagePNGRepresentation(compositeImage);
placeToSave = data;
NSLog(#"MapView Snapshot Saved.");
//show image for debugging
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 200, 320, 320)];
imageView.image = compositeImage;
[self.view addSubview:imageView];
}
UIGraphicsEndImageContext();
}];
[mapView setHidden:YES];
}
}

MKMapSnapshotter satellite error image when camera zoom / altitude level is close in

I am getting an error image when I use MKMapsnapshotter.
I tried manually zooming out (setting a higher camera altitude) and that seems to fix the issue, but I am not sure how to handle this programmatically. Is there a way to detect this kind of error and then reset the camera altitude? Is this a MapKit bug? This happens when I use a satellite or hybrid map type.
http://imgur.com/vWsu3Nc
MKMapSnapshotter *snapshotter = [[MKMapSnapshotter alloc] initWithOptions:options];
[snapshotter startWithCompletionHandler:^(MKMapSnapshot *snapshot, NSError *error) {
if (!snapshot.image || error) {
// Can't create snapshot. Use Placeholder
mapImage.image = [UIImage imageNamed:#"NoPhoto.png"];
NSLog(#"Snapshot Error: %#",[error localizedDescription]);
}
Any help would be greatly appreciated. Thanks!
EDIT:
After more research and testing, I was able to figure out a workaround.
This error image was mainly happening in satellite imagery over oceans/seas. I found that I can use reverse geocoder and check the placemark array to see if the location is in a city. If not, then it is probably over an ocean.
// Check location of satellite imagery
CLLocation *location = [[CLLocation alloc] initWithLatitude:myLatitude longitude:myLongitude];
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder reverseGeocodeLocation:location completionHandler:^(NSArray* placemarks, NSError* error){
// check to see if location is in a city
if ([placemarks[0] locality]) {
// set up camera
MKMapCamera *myCamera = [MKMapCamera
cameraLookingAtCenterCoordinate:location.coordinate
fromEyeCoordinate:location.coordinate
eyeAltitude:250];
// set snapshot options
MKMapSnapshotOptions *options = [[MKMapSnapshotOptions alloc] init];
options.camera = myCamera;
options.showsBuildings = YES;
options.showsPointsOfInterest = NO;
options.mapType = MKMapTypeSatellite;
options.scale = [UIScreen mainScreen].scale;
options.size = myImage.frame.size;
MKMapSnapshotter *snapshotter = [[MKMapSnapshotter alloc] initWithOptions:options];
[snapshotter startWithCompletionHandler:^(MKMapSnapshot *snapshot, NSError *error) {
if(!error) {
myImage.image = snapshot.image;
}
}]; // end snapshotter completion handler
} else {
// probably in the ocean
myImage.image = [UIImage imageNamed:#"NoPhoto.png"];
}
}];

Show MKMapView in UITableViewCell without slow down UI

i'm trying to put a MKMapView in some UiTableViewCell but (on iPhone 5, so even in other devices), when the app load the cell with the map, the scroll become not too smooth.
There is some method, with GCD or something, to do this in better way?
Here is a screenshot of the result:
I load the Cell from Nib, here is the code where i set the coordinate and the annotation (with custom view, but this is not the problem.)
// Delegate
cell.objectMap.delegate = self;
// Coordinate
MKCoordinateRegion region;
region.center = activity.object.location.coordinate;
MKCoordinateSpan span;
span.latitudeDelta = 0.055;
span.longitudeDelta = 0.055;
region.span = span;
[cell.objectMap setRegion:region animated:NO];
// Add an annotation
MKPointAnnotation *point = [[MKPointAnnotation alloc] init];
point.coordinate = activity.object.location.coordinate;
[cell.objectMap addAnnotation:point];
// Show the MKMapView in the cell
cell.objectMap.hidden = NO;
I ended up using MKMapSnapshotter for the devices that support this awesome and fast function and using Google Static Maps Api for the devices that run iOS6. I think is the best and fast solution that i can get.
Thanks to #Rob for the suggestion.
if ( IS_IOS7 ) {
// Placeholder
cell.objectImage.image = [UIImage imageNamed:#"mapPlaceholder"];
// Cooridinate
MKCoordinateRegion region;
region.center = activity.object.location.coordinate;
MKCoordinateSpan span;
span.latitudeDelta = 0.055;
span.longitudeDelta = 0.055;
region.span = span;
[self.mapViewForScreenshot setRegion:region animated:NO];
MKMapSnapshotOptions *options = [[MKMapSnapshotOptions alloc] init];
options.region = self.mapViewForScreenshot.region;
options.scale = [UIScreen mainScreen].scale;
options.size = CGSizeMake(300, 168);
MKMapSnapshotter *snapshotter = [[MKMapSnapshotter alloc] initWithOptions:options];
[snapshotter startWithCompletionHandler:^(MKMapSnapshot *snapshot, NSError *error) {
UIImage *image = snapshot.image;
[cell.objectImage setImage:image];
cell.mapPinImageView.hidden = NO;
}];
}else{
cell.mapPinImageView.hidden = NO;
[cell.objectImage setImageWithURL:[NSURL URLWithString:[NSString stringWithFormat:#"http://maps.googleapis.com/maps/api/staticmap?center=%f,%f&zoom=14&size=600x338&maptype=roadmap&sensor=false&key=APIKEY",activity.object.location.coordinate.latitude, activity.object.location.coordinate.longitude]] placeholderImage:nil];
}
Based on your answer, you can also create the snapshot in background and execute the result on the main queue (don't forgive to check the error before update your cell):
if ( IS_IOS7 ) {
cell.objectImage.image = [UIImage imageNamed:#"mapPlaceholder"];
// Cooridinate
MKCoordinateRegion region;
region.center = activity.object.location.coordinate;
MKCoordinateSpan span;
span.latitudeDelta = 0.055;
span.longitudeDelta = 0.055;
region.span = span;
[self.mapViewForScreenshot setRegion:region animated:NO];
MKMapSnapshotOptions *options = [[MKMapSnapshotOptions alloc] init];
options.region = self.mapViewForScreenshot.region;
options.scale = [UIScreen mainScreen].scale;
options.size = CGSizeMake(300, 168);
MKMapSnapshotter *snapshotter = [[MKMapSnapshotter alloc] initWithOptions:options];
dispatch_queue_t executeOnBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
[snapshotter startWithQueue:executeOnBackground completionHandler:^(MKMapSnapshot *snapshot, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (!error) {
cell.objectImage.image = snapshot.image;
cell.mapPinImageView.hidden = NO;
}
});
}];
}];
} else {
cell.mapPinImageView.hidden = NO;
[cell.objectImage setImageWithURL:[NSURL URLWithString:#""] placeholderImage:nil];
}

Resources