how to put MKAnnotation without affecting the MKMapView performance - objective-c

I have more than 2800 locations to be put on my map.
But when i am putting them the map is freezing. i can do nothing but wait until all the annotation data is available.
- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated{
NSAutoreleasePool *pool_mr = [[NSAutoreleasePool alloc] init];
NSLog(#"mapView:regionDidChangeAnimated:");
NSLog(#"latitude: %f, longitude: %f", regionsMapView.centerCoordinate.latitude, regionsMapView.centerCoordinate.longitude);
NSLog(#"latitudeDelta: %f, longitudeDelta: %f", regionsMapView.region.span.latitudeDelta, regionsMapView.region.span.longitudeDelta);
if (regionsMapView.region.span.latitudeDelta < 0.007) {
NSLog(#"SHOW ANNOTATIONS");
NSArray *annotations = [regionsMapView annotations];
AddressAnnotation *annotation = nil;
for (int i=0; i<[annotations count]; i++)
{
NSLog(#"%i", i);
annotation = (AddressAnnotation*)[annotations objectAtIndex:i];
[[regionsMapView viewForAnnotation:annotation] setHidden:NO];
}
}else {
NSLog(#"HIDE ANNOTATIONS");
NSArray *annotations = [regionsMapView annotations];
AddressAnnotation *annotation = nil;
for (int i=0; i<[annotations count]; i++)
{
NSLog(#"%i", i);
annotation = (AddressAnnotation*)[annotations objectAtIndex:i];
[[regionsMapView viewForAnnotation:annotation] setHidden:YES];
}
}
[pool_mr release];
}
And the Another method is like below:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation, AddressAnnotation>) annotation {
//NSAutoreleasePool *pool5 = [[NSAutoreleasePool alloc] init];
// if it's the user location, just return nil.
if ([annotation isKindOfClass:[MKUserLocation class]]){
NSLog(#"MKUserLocation");
return nil;
}
else {
NSLog(#"mapView:viewForAnnotation>>>");
NSLog(#"%#", [annotation markerColor]);
NSLog(#"image: %#", [NSMutableString stringWithFormat:#"MKPinAnnotationView_%#.png",[annotation markerColor]]);
MKPinAnnotationView *annView=[[[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:[annotation markerColor]] autorelease];
//annView.pinColor = MKPinAnnotationColorPurple;
UIImage *annotationImage = [[UIImage imageNamed:[NSMutableString stringWithFormat:#"MKPinAnnotationView_%#.png",[annotation markerColor]]] autorelease];
annView.image = annotationImage;
annView.animatesDrop = NO;
annView.canShowCallout = YES;
//annView.draggable = NO;
//annView.highlighted = NO;
annView.calloutOffset = CGPointMake(-5, 5);
return annView;
}
//[pool5 release];
NSLog(#"<<<mapView:viewForAnnotation");
return nil;
}

With that number of annotations, I would cluster them so as you zoom in, you get more detail. I put several thousand on a map that way and it works a treat. There is lots of information about map pin clustering if you search for it.
Here's one commercial option for example (no connection, haven't used) but if you look around you'll see plenty of information on how to implement it yourself.

Related

Order UITableView cells by calculated distance (Parse)

I have a problem with the database created on parse. I have a table that shows me how many miles of the various local (once you download the list from DB) the problem is that I would order it for distance upward, but I do not know how to do. The code I used to calculate the distance is this:
- (id)initWithCoder:(NSCoder *)aCoder
{
self = [super initWithCoder:aCoder];
if (self) {
// Custom the table
// The className to query on
self.parseClassName = #"Ristoranti";
// The key of the PFObject to display in the label of the default cell style
self.textKey = #"NomeLocale";
// Whether the built-in pull-to-refresh is enabled
self.pullToRefreshEnabled = YES;
// Whether the built-in pagination is enabled
self.paginationEnabled = YES;
// The number of objects to show per page
self.objectsPerPage = 100;
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
locationManager.distanceFilter = kCLDistanceFilterNone;
locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation;
if(IS_OS_8_OR_LATER) {
[locationManager requestWhenInUseAuthorization];
[locationManager requestAlwaysAuthorization];
}
[locationManager startUpdatingLocation];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(refreshTable:)
name:#"refreshTable"
object:nil];
}
- (void)refreshTable:(NSNotification *) notification
{
// Reload the recipes
[self loadObjects];
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"refreshTable" object:nil];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
- (PFQuery *)queryForTable
{
PFQuery *query = [PFQuery queryWithClassName:self.parseClassName];
query.limit = 100;
[query orderByAscending:#"createdAt"];
return query;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath object:(PFObject *)object
{
static NSString *simpleTableIdentifier = #"RecipeCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier];
}
NSString *AntDef = [NSString stringWithFormat:#"%#", [object objectForKey:#"AnteprimaDefault"]];
if ([AntDef isEqualToString:#"Null"])
{
PFImageView *thumbnailImageView = (PFImageView*)[cell viewWithTag:100];
thumbnailImageView.image = [UIImage imageNamed:#"ImageDefault.png"];
thumbnailImageView.layer.cornerRadius = thumbnailImageView.frame.size.width / 2;
thumbnailImageView.clipsToBounds = YES;
thumbnailImageView.layer.borderWidth = 0.5;
thumbnailImageView.layer.borderColor = [UIColor lightGrayColor].CGColor;
}
else
{
PFFile *thumbnail = [object objectForKey:#"Anteprima1"];
PFImageView *thumbnailImageView = (PFImageView*)[cell viewWithTag:100];
thumbnailImageView.image = [UIImage imageNamed:#"placeholder.jpg"];
thumbnailImageView.file = thumbnail;
thumbnailImageView.layer.cornerRadius = thumbnailImageView.frame.size.width / 2;
thumbnailImageView.clipsToBounds = YES;
[thumbnailImageView loadInBackground];
}
Lat = [[object objectForKey:#"Latitudine"] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
Long = [[object objectForKey:#"Longitudine"] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
Latdouble = [Lat doubleValue];
Longdouble = [Long doubleValue];
NSArray *locations = [NSArray arrayWithObjects:[[CLLocation alloc] initWithLatitude:Latdouble longitude:Longdouble], nil];
//NSLog(#"LOCATIONS COORDINATE: %#", locations);
CLLocation *currentLocation = [[CLLocation alloc] initWithLatitude:locationManager.location.coordinate.latitude longitude:locationManager.location.coordinate.longitude];;
CLLocation *nearestLoc = nil;
CLLocationDistance nearestDis = FLT_MAX;
for (CLLocation *location in locations) {
CLLocationDistance distance = [currentLocation distanceFromLocation:location];
for (CLLocation *location in locations) {
distance = [currentLocation distanceFromLocation:location];
if (nearestDis > distance) {
nearestLoc = location;
nearestDis = distance;
}
}
text12 = [NSString stringWithFormat:#"%.1f Km", nearestDis/1000];
}
...
}
Thanks in advance :)
Assuming your response data from Parse.com is a NSArray.
I'd suggest:
Do the request.
Sort the NSArray
Reload Data.
Sorting the array could be done like this using the block: sortedArrayUsingComparator:^NSComparisonResult(id a, id b)
NSArray *sortedArray;
sortedArray = [initialDataArray sortedArrayUsingComparator:^NSComparisonResult(id a, id b) {
//Here do the method to compare if `a` is closer or `b` is closer
YourParseObject *aObject = (YourParseObject *)a;
YourParseObject *bObject = (YourParseObject *)b;
//Get Latitude & Longitude from a and b.
//return NSOrderedAscending or NSOrderedDescending according to which one of a or b is the closest, using your previous method I suppose to be working.
}];
So sortedArray will look like this:
{
YourParseObject the closest one
YourParseObject the second closest one
//...
YourParseObject the farthest one.
}
Once it's done:
[yourTableView reloadData]
I just encountered this issue today and found out that Parse.com has already implemented this solution:
If you're using Parse's PFQueryTableViewController.h, you'd want to add this into your queryForTable method:
(PFQuery *)queryForTable {
PFQuery *query = [PFQuery queryWithClassName:self.parseClassName];
[query whereKey:#"yourClassPFLocationField" nearGeoPoint:yourLocation];
return query;
}
The same applies if you're not using the PFQueryTable, just use that sorting method when loading the query.
Everything is on the PFQuery documentation :D

GMMGeoTileImageData error with MapKit

In my viewcontroller I use Maps and I load a list of pins.
When I move the map or zoom in or out it, my app crashes and displays this error:
[GMMGeoTileImageData isEqualToString:]: unrecognized selector sent to instance 0x862d3b0
This is my code of the view controller:
- (void)viewDidLoad
{
statoAnn = [[NSMutableString alloc] initWithFormat:#"false"];
//bottone annulla per tornare indietro
UIBarButtonItem *annullaButton = [[[UIBarButtonItem alloc] initWithTitle:#"Annulla" style:UIBarButtonItemStylePlain target:self action:#selector(backView)] autorelease];
self.navigationItem.leftBarButtonItem = annullaButton;
//inizializzo la mappa
mapView = [[MKMapView alloc] initWithFrame:CGRectMake(0, 0, 320, 416)];
mapView.delegate = self;
mapView.mapType = MKMapTypeStandard;
[self.view addSubview:mapView];
[self setGmaps:arrData];
[super viewDidLoad];
}
/** inizializzo l'annotation del poi mappa **/
- (void) setGmaps:(NSMutableArray*)inputData {
// setto la lat e lng
CLLocationDegrees latitude;
CLLocationDegrees longitude;
CLLocationCoordinate2D poiLocation;
arrAnn = [[NSMutableArray alloc] init];
for(int i=0; i<[inputData count]; i++) {
//ricavo la lat e lng del pin
latitude = [[[inputData objectAtIndex:i] objectForKey:#"latitude"] doubleValue];
longitude = [[[inputData objectAtIndex:i] objectForKey:#"longitude"] doubleValue];
// setto la location del poi
poiLocation.latitude = latitude;
poiLocation.longitude = longitude;
//[[[CLLocation alloc] initWithLatitude:latitude longitude:longitude] autorelease];
//setto il pin
Annotation *ann = [[Annotation alloc] initWithCoordinate:poiLocation];
ann.title = [[inputData objectAtIndex:i] objectForKey:#"label"];
[arrAnn addObject:ann];
[ann release];
}
if (nil != self.arrAnn) {
[self.mapView addAnnotations:arrAnn];
//self.ann = nil;
self.arrAnn = nil;
}
}
/** setto il pin nella mappa ***/
- (void)setCurrentLocation:(CLLocation *)location {
MKCoordinateRegion region = {{0.0f, 0.0f}, {0.0f, 0.0f}};
region.center = location.coordinate;
region.span.longitudeDelta = 0.1f;
region.span.latitudeDelta = 0.1f;
[self.mapView setRegion:region animated:YES];
[self.mapView regionThatFits:region];
}
- (MKAnnotationView *)mapView:(MKMapView *)mapViewTemp viewForAnnotation:(id <MKAnnotation>)annotation {
MKPinAnnotationView *view = nil; // return nil for the current user location
view = (MKPinAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:#"identifier"];
if (nil == view) {
view = [[[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"identifier"] autorelease];
view.rightCalloutAccessoryView = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
}
[view setPinColor:MKPinAnnotationColorPurple];
[view setCanShowCallout:YES];
[view setAnimatesDrop:YES];
if (![statoAnn isEqualToString:#"true"]) {
CLLocation *location = [[CLLocation alloc] initWithLatitude:annotation.coordinate.latitude
longitude:annotation.coordinate.longitude];
[self setCurrentLocation:location];
statoAnn = [NSMutableString stringWithFormat:#"true"];
}
return view;
}
In viewForAnnotation, this line:
statoAnn = [NSMutableString stringWithFormat:#"true"];
sets statoAnn to an autoreleased string.
When the method exits, release is called on statoAnn and it no longer owns the memory it was pointing to. When the method is called again when you zoom or move the map, the memory that statoAnn was pointing to is now used by something else (GMMGeoTileImageData in this case). That object is not an NSString and doesn't have an isEqualToString: method and you get the error you are seeing.
To fix this, set statoAnn so the value is retained like you are doing in viewDidLoad. For example, you could change it to:
statoAnn = [[NSMutableString alloc] initWithFormat:#"true"];
You could also declare statoAnn as a property (#property (nonatomic, copy) NSString *statoAnn) and just set it using self.statoAnn = #"true";. The property setter will do the retaining for you.
However, you don't need to use a string to hold a "true" and "false" value. It's much easier and efficient to use a plain BOOL and you won't have to worry about retain/release since it's a primitive type and not an object.
The other thing is that viewForAnnotation is not the right place to be setting the map view's region in the first place. You can do that in viewDidLoad after the annotations are added.
Another thing: At the top of viewForAnnotation, you have the comment "return nil for the current user location" but that code doesn't do that. It just initializes the view to nil. To actually do what the comment says, you need this:
MKPinAnnotationView *view = nil;
// return nil for the current user location...
if ([annotation isKindOfClass:[MKUserLocation class]])
return nil;
Finally, if the dequeueReusableAnnotationViewWithIdentifier does return a view (if view != nil), you need to set view.annotation to the current annotation since the re-used view may have been for a different annotation.

MKMapView addAnnotation not on the MapView

I need some help because i can't to display annotation in my mapView.
I'm trying to update the annotations with this function :
- (void)addAnnotationsInMap:(NSMutableDictionary *)dico
{
NSLog(#"Agences Dictionary dico4324 : %#", dico);
if ([dico count] > 0)
{
NSLog(#"Count : %d", [dico count]);
for(int i = 0; i < [dico count]; i++)
{
CGFloat latDelta = [[[dico objectForKey:[NSString stringWithFormat:#"%d", i]] objectForKey:#"latitude"] floatValue];
CGFloat longDelta = [[[dico objectForKey:[NSString stringWithFormat:#"%d", i]] objectForKey:#"longitude"] floatValue];
CLLocationCoordinate2D newCoord =
{
(latDelta),
(longDelta)
};
MapPointAnnotations *mp = [[MapPointAnnotations alloc]
initWithCoordinate:newCoord
title:[[dico objectForKey:[NSString stringWithFormat:#"%d", i]] objectForKey:#"name"]
subTitle:[[dico objectForKey:[NSString stringWithFormat:#"%d", i]] objectForKey:#"street"]];
[agenceMapView addAnnotation:mp];
}
}
}
If i call my function in the same class (LocalizeViewController) with [self addAnnotationsInMap:Dico], the annotations are on the mapView (agenceMapView).
I need to call my function from an other class (RightSideBarViewController), but the annotations not display on the mapView if i call on the RightSideBarViewController class.
Where i call the function addAnnotationsInMap (Class RightSideBarViewController) :
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *identifier = #"LocaliserTop";
UIStoryboard *storyboard;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
{
storyboard = [UIStoryboard storyboardWithName:#"iPhone" bundle:nil];
} else if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
storyboard = [UIStoryboard storyboardWithName:#"iPad" bundle:nil];
}
LocalizeViewController *localizeViewController = (LocalizeViewController *)[storyboard instantiateViewControllerWithIdentifier:identifier];
[self.slidingViewController anchorTopViewOffScreenTo:ECLeft animations:nil onComplete:^{
CGRect frame = self.slidingViewController.topViewController.view.frame;
self.slidingViewController.topViewController = localizeViewController;
self.slidingViewController.topViewController.view.frame = frame;
[self.slidingViewController resetTopView];
}];
[localizeViewController addAnnotationsInMap:agencesDictionary];
}
Do you have some ideas ?
You should not call
[localizeViewController addAnnotationsInMap:agencesDictionary];
right after
LocalizeViewController *localizeViewController = (LocalizeViewController *)[storyboard instantiateViewControllerWithIdentifier:identifier];
because agenceMapView is still nil. So you are basically doing
[nil addAnnotation:mp];
To correct that, you should wait until the localizeViewController loads from the nib file (which take some time).
For example, you can put
localizeViewController.annotations = agencesDictionary;
instead of
[localizeViewController addAnnotationsInMap:agencesDictionary];
Then in viewDidLoad
[self addAnnotationsInMap:annotations];

Add a image to MKPointAnnotation

Is it possible?
Im performing an action here that gives to me a pin. But i need a image. And MKAnnotation seems complicated for me.
- (void)abreMapa:(NSString *)endereco {
NSString *urlString = [NSString stringWithFormat:#"http://maps.google.com/maps/geo?q=%#&output=csv",
[endereco stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
NSString *locationString = [NSString stringWithContentsOfURL:[NSURL URLWithString:urlString]];
NSArray *listItems = [locationString componentsSeparatedByString:#","];
double latitude = 0.0;
double longitude = 0.0;
if([listItems count] >= 4 && [[listItems objectAtIndex:0] isEqualToString:#"200"]) {
latitude = [[listItems objectAtIndex:2] doubleValue];
longitude = [[listItems objectAtIndex:3] doubleValue];
}
else {
//Show error
}
CLLocationCoordinate2D coordinate;
coordinate.latitude = latitude;
coordinate.longitude = longitude;
myMap.region = MKCoordinateRegionMakeWithDistance(coordinate, 2000, 2000);
MKPointAnnotation *annotation = [[MKPointAnnotation alloc] init];
[annotation setCoordinate:coordinate];
[annotation setTitle:#"Some Title"];
[myMap addAnnotation:annotation];
// Coloca o icone
[self.view addSubview:mapa];
}
Thanks!
You'll need to set up an MKMapViewDelegate, and implement
- (MKAnnotationView *)mapView:(MKMapView *)theMapView viewForAnnotation:(id <MKAnnotation>)annotation
Here's sample code, stolen from the MapCallouts sample code provided on Apple's developer site. I've modified it to focus on the important details. You can see below that the key is to set the image on an annotation view, and return that annotation view from this method.
- (MKAnnotationView *)mapView:(MKMapView *)theMapView viewForAnnotation:(id <MKAnnotation>)annotation
{
static NSString *SFAnnotationIdentifier = #"SFAnnotationIdentifier";
MKPinAnnotationView *pinView =
(MKPinAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:SFAnnotationIdentifier];
if (!pinView)
{
MKAnnotationView *annotationView = [[[MKAnnotationView alloc] initWithAnnotation:annotation
reuseIdentifier:SFAnnotationIdentifier] autorelease];
UIImage *flagImage = [UIImage imageNamed:#"flag.png"];
// You may need to resize the image here.
annotationView.image = flagImage;
return annotationView;
}
else
{
pinView.annotation = annotation;
}
return pinView;
}
We use dequeueReusableAnnotationViewWithIdentifier to grab an already created view to make reuse of our annotation views. If one isn't returned we create a new one. This prevents us from creating hundreds of MKAnnotationViews if only a few are ever in sight at the same time.

Problems Removing Annotations from a MKMapView

I am trying to remove all the annotations of a map. The class that contains the MKMapView implements the MKMapViewDelegate. I am calling the remove all annotations method when a button (displayed in a modal view) is clicked. I am using the next code to remove all the annotations.
- (void)removeAllAnnotations {
NSMutableArray *annotationsToRemove = [NSMutableArray arrayWithCapacity:[self.mapView.annotations count]];
for (int i = 0; i < [self.mapView.annotations count]; i++) {
if ([[self.mapView.annotations objectAtIndex:i] isKindOfClass:[AddressAnnotation class]]) {
[annotationsToRemove addObject:[self.mapView.annotations objectAtIndex:i]];
}
}
[self.mapView removeAnnotations:annotationsToRemove];
}
The code works right but after calling the method, and when I try to add new annotations to the empty map, the class does not call to the viewForAnnotations method and the annotations do not drop down and do not show a disclosure button into the annotation view. Why is this happening?
Thanks for reading.
Edited:
The annotations are shown but with out calling the view for annotation method (with out dropping down and with out including a disclosure button into the annotation view). Here is the method that I use to add the annotations and the viewForAnnotation method.
- (void)loadAnnotations:(NSString *)type {
NSString *path = [[NSBundle mainBundle] pathForResource:#"PlacesInformation" ofType:#"plist"];
NSMutableArray *tmpArray = [[NSMutableArray alloc] initWithContentsOfFile:path];
for (int i = 0; i < tmpArray.count; i++) {
// Breaks the string down even further to latitude and longitude fields.
NSString *coordinateString = [[tmpArray objectAtIndex:i] objectForKey:#"coordinates"];
NSString *option = [[tmpArray objectAtIndex:i] objectForKey:#"type"];
if ([option isEqualToString:type]) {
CLLocationCoordinate2D currentCoordinate = [self stringToCoordinate:coordinateString];
AddressAnnotation *annotation = [[[AddressAnnotation alloc] initWithCoordinate:currentCoordinate] autorelease];
[self.mapView addAnnotation:annotation];
}
}
}
- (MKAnnotationView *)mapView:(MKMapView *)theMapView viewForAnnotation:(id <MKAnnotation>)annotation {
// If it's the user location, just return nil.
if ([annotation isKindOfClass:[MKUserLocation class]])
return nil;
else { // Handles the other annotations.
// Try to dequeue an existing pin view first.
static NSString *AnnotationIdentifier = #"AnnotationIdentifier";
MKPinAnnotationView *pinView = (MKPinAnnotationView *)[self.mapView dequeueReusableAnnotationViewWithIdentifier:AnnotationIdentifier];
if (!pinView) {
// If an existing pin view was not available, creates one.
MKPinAnnotationView *customPinView = [[[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:AnnotationIdentifier] autorelease];
customPinView.animatesDrop = YES;
customPinView.canShowCallout = YES;
// Adds a detail disclosure button.
UIButton *rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
[rightButton addTarget:self action:#selector(showDetails:) forControlEvents:UIControlEventTouchUpInside];
customPinView.rightCalloutAccessoryView = rightButton;
return customPinView;
} else
pinView.annotation = annotation;
}
return nil;
}
In viewForAnnotation, in the case where you are reusing a view, you are currently returning nil.
This else part:
} else
pinView.annotation = annotation;
Should probably be:
} else {
pinView.annotation = annotation;
return pinView;
}