Show MKMapView in UITableViewCell without slow down UI - ios

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];
}

Related

MapKit Annotations Appear in One View Controller, and not in another

In my browse view controller, the Map Kit annotations show up.
_mapVC = [[CollectionMapViewController alloc] init];
mapVC.collectedLeafArray = [collectionFetchedResultsController fetchedObjects];
mapVC.canShowCallout = YES;
mapVC.delegate = self;
[mapVC layoutMapView];
[self addChildViewController:mapVC];
[self.view insertSubview:mapVC.view belowSubview:_userCollectionSortMenu.view];
[mapVC didMoveToParentViewController:self];
I set up my child view controller the exact same way in my note taking view controller. However, they annotation pins don't show up.
_mapVC = [[CollectionMapViewController alloc] init];
_mapVC.collectedLeafArray = [NSArray arrayWithObject:_collectedLeaf];
_mapVC.delegate = self;
_mapVC.canShowCallout = NO;
[_mapVC layoutMapView];
[self addChildViewController:_mapVC];
[_scrollView addSubview:_mapVC.view];
[_mapVC didMoveToParentViewController:self];
I know the collected leaf is not nil, because I check the latitude in the CollectionMapViewController method that sets the annotations:
- (void)layoutMapView
{
NSMutableArray* leavesWithValidGeotag = [[NSMutableArray alloc] initWithCapacity:[collectedLeafArray count]];
for (CollectedLeaf* collectedLeaf in collectedLeafArray)
{
NSLog(#"latitude: %#", collectedLeaf.latitude);
if ( ![collectedLeaf.latitude isEqualToString:kGeoLocationNotAvailable] )
{
[leavesWithValidGeotag addObject:collectedLeaf];
LeafAnnotation* annotation = [[LeafAnnotation alloc] initWithLeaf:collectedLeaf];
[mapView addAnnotation:annotation];
[annotation release];
}
}
//// Adjust the region
if([leavesWithValidGeotag count] > 1)
{
NSArray* westEastSort = [leavesWithValidGeotag sortedArrayUsingSelector:#selector(longitudeCompare:)];
CollectedLeaf* eastMostLeaf = [westEastSort objectAtIndex:0];
CollectedLeaf* westMostLeaf = [westEastSort lastObject];
NSArray* southNorthSort = [leavesWithValidGeotag sortedArrayUsingSelector:#selector(latitudeCompare:)];
CollectedLeaf* southMostLeaf = [southNorthSort objectAtIndex:0];
CollectedLeaf* northMostLeaf = [southNorthSort lastObject];
CLLocationCoordinate2D center;
center.longitude = ([westMostLeaf.longitude doubleValue] + [eastMostLeaf.longitude doubleValue]) / 2.0f;
center.latitude = ([southMostLeaf.latitude doubleValue] + [northMostLeaf.latitude doubleValue]) / 2.0f;
MKCoordinateSpan span = MKCoordinateSpanMake(1.5 * fabs([northMostLeaf.latitude doubleValue] - [southMostLeaf.latitude doubleValue]),
1.5 * fabs([westMostLeaf.longitude doubleValue] - [eastMostLeaf.longitude doubleValue]));
if ( span.latitudeDelta < kMinimumSpan )
{
span.latitudeDelta = kMinimumSpan;
}
if ( span.longitudeDelta < kMinimumSpan )
{
span.longitudeDelta = kMinimumSpan;
}
MKCoordinateRegion region = MKCoordinateRegionMake(center, span);
[mapView setRegion:region];
}
else if([leavesWithValidGeotag count] == 1)
{
CollectedLeaf* theLeaf = [leavesWithValidGeotag objectAtIndex:0];
CLLocationCoordinate2D center;
center.longitude = [theLeaf.longitude doubleValue];
center.latitude = [theLeaf.latitude doubleValue];
MKCoordinateSpan span = MKCoordinateSpanMake(kMinimumSpan, kMinimumSpan);
MKCoordinateRegion region = MKCoordinateRegionMake(center, span);
[mapView setRegion:region];
}
[leavesWithValidGeotag release];
}
Maybe, you need to copy NSArray object.
NSArray *test1 = [NSArray arrayWithObject:_collectedLeaf];
_mapVC.collectedLeafArray = [test1 copy];

How can I show start location at top and end location at bottom on mapview for route?

I am showing route between start location and end location on map for iPhone app, i want to show start location at top in map and end location at bottom, also make sure that all route should be visible on map. Below is code i tried.
-(void) viewDidLoad {
MKPointAnnotation *myAnnotation = [[MKPointAnnotation alloc] init];
myAnnotation.coordinate = CLLocationCoordinate2DMake(42.110532,
-87.912126);
myAnnotation.title = #"Start point";
[self.mapView addAnnotation:myAnnotation];
MKPointAnnotation *destinatioAnnotation = [[MKPointAnnotation alloc]
init];
destinatioAnnotation.coordinate = CLLocationCoordinate2DMake(42.105948,
-87.870664);
destinatioAnnotation.title = #"End Point";
[self.mapView addAnnotation:destinatioAnnotation];
CLLocationCoordinate2D startCoord1 =
CLLocationCoordinate2DMake(42.110532, -87.912126);
MKCoordinateRegion adjustedRegion1 = [self.mapView
regionThatFits:MKCoordinateRegionMakeWithDistance(startCoord1, 300000,
300000)];
[self.mapView setRegion:adjustedRegion1 animated:YES];
self.mapView.showsCompass = NO;
MKDirectionsRequest *directionsRequest = [MKDirectionsRequest new];
directionsRequest.requestsAlternateRoutes = NO;
// Make the destination
CLLocationCoordinate2D sourceCoords =
CLLocationCoordinate2DMake(42.110532, -87.912126);
MKPlacemark *sourcePlacemark = [[MKPlacemark alloc]
initWithCoordinate:sourceCoords addressDictionary:nil];
MKMapItem *sourceLoc = [[MKMapItem alloc]
initWithPlacemark:sourcePlacemark];
// Set the source and destination on the request
// Make the destination
CLLocationCoordinate2D destinationCoords = CLLocationCoordinate2DMake(42.105948, -87.870664);
MKPlacemark *destinationPlacemark = [[MKPlacemark alloc] initWithCoordinate:destinationCoords addressDictionary:nil];
MKMapItem *destination = [[MKMapItem alloc] initWithPlacemark:destinationPlacemark];
// Set the source and destination on the request
[directionsRequest setSource:sourceLoc];
[directionsRequest setDestination:destination];
MKDirections *directions = [[MKDirections alloc] initWithRequest:directionsRequest];
[directions calculateDirectionsWithCompletionHandler:^(MKDirectionsResponse *
_Nullable response, NSError * _Nullable error) {
// Now handle the result
if (error) {
NSLog(#"There was an error getting your directions");
return;
}
// So there wasn't an error - let's plot those routes
_currentRoute = [response.routes firstObject];
[self plotRouteOnMap:_currentRoute];
}];
}
- (void)plotRouteOnMap:(MKRoute *)route {
if(_routeOverlay) {
[self.mapView removeOverlay:_routeOverlay];
}
// Update the ivar
_routeOverlay = route.polyline;
self.routePolyline = route.polyline;
self.mapView.tag = 1;
// Add it to the map
[self.mapView setVisibleMapRect:[route.polyline boundingMapRect] edgePadding:UIEdgeInsetsMake(50, 50, 50, 40) animated:YES];
/*[self.mapView setRegion:MKCoordinateRegionForMapRect([route.polyline
boundingMapRect])
animated:YES];*/
[self.mapView addOverlay:_routeOverlay level:MKOverlayLevelAboveRoads];
[self performSelector:#selector(changeHeading) withObject:nil
afterDelay:1.2];
}
- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id<MKOverlay>)overlay {
NSLog(#"called");
MKPolylineRenderer *renderer = [[MKPolylineRenderer alloc] initWithPolyline:overlay];
if (mapView.tag == 1) {
renderer.strokeColor = [UIColor redColor];
}if (mapView.tag == 2) {
renderer.strokeColor = [UIColor blueColor];
}else{
renderer.strokeColor = [UIColor blueColor];
}
renderer.lineWidth = 4.0;
NSLog(#"x: %f, y:%f, widht: %f, height: %f",[overlay boundingMapRect].origin.x,[overlay
boundingMapRect].origin.y,[overlay
boundingMapRect].size.width,[overlay boundingMapRect].size.height);
return renderer;
}
-(void) changeHeading {
MKMapCamera *camera = self.mapView.camera;
CLLocationDistance altitude = camera.altitude;
NSLog(#"altitude: %f",altitude);
CLLocationDirection mapAngle = camera.heading;
NSLog(#"mapAngle: %f",mapAngle);
if (altitude < 3000 && altitude > 1000) {
// do something
}
if (self.mapView.camera.heading != 257) {
MKMapCamera *newCamera = [MKMapCamera camera];
newCamera.centerCoordinate = self.mapView.camera.centerCoordinate;
newCamera.heading = 257;
newCamera.altitude = self.mapView.camera.altitude;
[self.mapView setCamera:newCamera animated:YES];
}
/*MKMapCamera *turnCam = [MKMapCamera cameraLookingAtCenterCoordinate:CLLocationCoordinate2DMake(42.110532,
-87.912126)
fromEyeCoordinate:CLLocationCoordinate2DMake(42.105948, -87.870664)
eyeAltitude:self.mapView.camera.altitude];
self.mapView.camera = turnCam;*/
}

Clear MKSnapshotter memory

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...

iOS 7 - region.center deprecated

I have this code for my iOS app:
NSString *location = [[NSString alloc] initWithFormat:#"%#, %#", [self.campus campusStreetAddress], [self.campus campusCityStateZip]];
CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder geocodeAddressString:location
completionHandler:^(NSArray* placemarks, NSError* error){
if (placemarks && placemarks.count > 0) {
CLPlacemark *topResult = [placemarks objectAtIndex:0];
MKPlacemark *placemark = [[MKPlacemark alloc] initWithPlacemark:topResult];
MKCoordinateRegion region = self.campusMap.region;
region.center = placemark.region.center; //DEPRECATED iOS 7
region.span.longitudeDelta /= 1500;
region.span.latitudeDelta /= 1500;
[self.campusMap setRegion:region animated:NO];
[self.campusMap addAnnotation:placemark];
}
}
];
But, when I upgraded my app to iOS 7, placemark.region.center is deprecated. Is there a replacement I should use? Is this even a proper method to creating a map in a view?
Thanks!!
Try this:
region.center = [(CLCircularRegion *)placemark.region center];
if you just want the center of the region you can use :
region.center = placemark.location.coordinate
Combination of Heesien's and other answers and a bit of experimenting.
- (void)centerMapAroundPlacemark:(MKPlacemark *)placemark
{
CLRegion *region = placemark.region;
if ([region isKindOfClass:[CLCircularRegion class]])
{
[self centerMapAroundCircularRegion:(CLCircularRegion *)region
centerCoodinate:placemark.location.coordinate];
}
else
{
[self centerMapAroundCoorinate:placemark.location.coordinate];
}
}
- (void)centerMapAroundCircularRegion:(CLCircularRegion *)circularRegion
{
MKCoordinateRegion coordinateRegion =
MKCoordinateRegionMakeWithDistance(circularRegion.center,
circularRegion.radius,
circularRegion.radius);
[self.mapView setRegion:coordinateRegion animated:YES];
}
- (void)centerMapAroundCircularRegion:(CLCircularRegion *)circularRegion
centerCoodinate:(CLLocationCoordinate2D)centerCoodinate
{
// Only user the radius of region for an appropriate zoom level.
// The center of the region is not accurate.
// To see this search for 'Bath, UK'
MKCoordinateRegion coordinateRegion =
MKCoordinateRegionMakeWithDistance(centerCoodinate,
circularRegion.radius,
circularRegion.radius);
[self.mapView setRegion:coordinateRegion animated:YES];
}

Two Annotation Pins at a time in MKMapView

I successfully implemented MKMapVIew and a single annotation on my Map. I am not able to represent two postitions simultaneously. I am using MKMapViewDelegate method :
mapView:viewForAnnotation:
Can someone look into this thing.
Thanks!
EDIT
- (void)viewDidLoad
{
[super viewDidLoad];
[mapView setMapType:MKMapTypeStandard];
[mapView setZoomEnabled:YES];
[mapView setScrollEnabled:YES];
MKCoordinateRegion region = { {0.0, 0.0 }, { 0.0, 0.0 } };
region.center.latitude = 22.569722 ;
region.center.longitude = 88.369722;
region.span.longitudeDelta = 0.1f;
region.span.latitudeDelta = 0.1f;
MKCoordinateRegion anotherRegion = { {0.0, 0.0 }, { 0.0, 0.0 } };
anotherRegion.center.latitude = 28.38 ;
anotherRegion.center.longitude = 77.12;
anotherRegion.span.longitudeDelta = 90.0f;
anotherRegion.span.latitudeDelta = 90.0f;
[mapView setRegion:region animated:YES];
[mapView setDelegate:self];
DisplayMap *ann = [[DisplayMap alloc] init];
ann.title = #" Kolkata";
ann.subtitle = #"Mahatma Gandhi Road";
ann.coordinate = region.center;
[mapView addAnnotation:ann];
DisplayAnotherMap *annMap = [[DisplayAnotherMap alloc] init];
annMap.title = #" New Delhi";
annMap.subtitle = #"Shahdara";
annMap.coordinate = anotherRegion.center;
[mapView addAnnotations:[NSArray arrayWithObjects:annMap,ann,nil]];
}
This will fulfill the requirement for you! ...:)
The method mapView:viewForAnnotation: is just for the view of a the annotations, eg. the colour of the pin and the title label etc. If you want to show multiple annotations you should alloc and init the all with the required positions. For example, you could create a .plist with all the coordinates and the in for cycle just add them.
EDIT
This is a sample code, taken from somewhere. You must alloc and init all anotations.
-(void)loadDummyPlaces{
srand((unsigned)time(0));
NSMutableArray *tempPlaces=[[NSMutableArray alloc] initWithCapacity:0];
for (int i=0; i<1000; i++) {
MyPlace *place=[[MyPlace alloc] initWithCoordinate:CLLocationCoordinate2DMake([self RandomFloatStart:42.0 end:47.0],[self RandomFloatStart:14.0 end:19.0])];
[place setCurrentTitle:[NSString stringWithFormat:#"Place %d title",i]];
[place setCurrentSubTitle:[NSString stringWithFormat:#"Place %d subtitle",i]];
[place addPlace:place];
[tempPlaces addObject:place];
[place release];
}
places=[[NSArray alloc] initWithArray:tempPlaces];
[tempPlaces release];
}

Resources