How to manage a huge amount of MapViewAnnotations - iphone

I have a MapView with a huge amount of annotations (8000+). The performance is going down by scrolling the map while all annotations are visible. So, I had the idea to hide some annotations when the are in the visible area. I found this thread in the forum, but it doesn't solve my problem.
I tried another solution by writing this piece of code
- (void)mapView:(MKMapView *)_mapView regionDidChangeAnimated:(BOOL)animated
{
NSSet *annoSet = [[NSSet alloc] initWithSet:[_mapView annotationsInMapRect:_mapView.visibleMapRect]];
NSMutableSet *annotationsSet = (NSMutableSet *)annoSet;
NSLog(#"Annos: %i", [annotationsSet count]);
if([annotationsSet count]>500)
{
for(MapViewAnnotation* annotation in annotationsSet)
{
[[_mapView viewForAnnotation:annotation] setHidden:YES];
}
}
}
This works, but after that operation the mapView is near a total breakdown, what can I improve?
Edit:
My new code looks like this:
- (void)mapView:(MKMapView *)_mapView regionDidChangeAnimated:(BOOL)animated
{
NSSet *annoSet = [[NSSet alloc] initWithSet:[_mapView annotationsInMapRect:_mapView.visibleMapRect]];
//NSMutableSet *annotationsSet = (NSMutableSet *)annoSet;
NSLog(#"Annos: %i", [annoSet count]);
if([annoSet count]>500)
{
for(MapViewAnnotation* annotation in annoSet)
{
[[_mapView viewForAnnotation:annotation] setHidden:YES];
}
}
else if([annoSet count] <= 500)
{
for(MapViewAnnotation* annotation in annoSet)
{
[[_mapView viewForAnnotation:annotation] setHidden:NO];
}
}
}
but i dont want to hide all annotations in the visibleRect just a few of them.
Any idea how i can do that in an efficient way?

What you're looking for is a way to cluster your annotations, which means to display a single annotation where there are several annotations in close proximity. This can get somewhat complicated if you want to do it right, but if you search the Net, you will find several places that discuss clustering strategies and algorithms. I've linked to a number of them below.
This blog post on the Applidium website discusses one such strategy, and there are now several projects on Github (mapkit-clusters, kingpin, RevClusterMap) that can take care of clustering for you. If nothing else, take a look at the code and you'll get some idea of how to deal with the problem.
Back when my team was working on the problem, however, we went with a solution described in one of the WWDC videos (this one, I think (developer account required)), which we modified somewhat to improve performance.
Note that you will probably have to find a way to let the user know when they click on a clustered annotation. We did this with a custom callout that listed all the annotations represented by that single annotation.

Related

UIImageView comparing images in game

I am working on a rock paper scissor shoe game, and I have no idea on how to do this. I have two UIImageView(s), and within each one is an image. I want to be able to compare the two images. I have looked up online, and cannot find anything. I want to be able to say
if([imageView1.image == rock] && [imageView2.image == scissor]) {
textLabel.text = #"You won!";
}
I know that this syntax is wrong of course, but I am trying to show the english part of what I am looking for. Any help is appreciated.
I do not have any kind of source code to show you as I do not know what I am doing with this. I am not looking for pixel to pixel comparison, or anything complex, I am only looking for a way to determine if the image is the same or not.
OK, Here is how I would solve this problem using enums. Firstly, declare your enum. You can call it whatever you like, i cam calling it RPS_STATE
enum RPS_STATE {
RPS_ROCK,
RPS_SCISSOR,
RPS_PAPER,
RPS_UNDEFINED
};
it's always useful to include an undefined state for init purposes. Now, these things defined in the enum are actually just integers from 0-3.
So you can use the following when you are setting your images.
-(void) setToRock:(UIImageView *) view {
view.image = rockImage;
view.tag = RPS_ROCK;
}
-(void) setToScissor:(UIImageView *) view {
view.image = scissorImage;
view.tag = RPS_SCISSOR;
}
-(void) setToPaper:(UIImageView *) view {
view.image = paperImage;
view.tag = RPS_PAPER;
}
then you can set and compare them nice and easy.
[self setToPaper:imageView1];
[self setToPaper:imageView2];
if(imageView1.tag == imageView2.tag){
}
and so on. You can also use enums as a type. eg
enum RPS_STATE variableHoldingOnlyRPSState;
Hope that helps :)

How to get NSString variable value from NSObject to ViewController

I am trying to set up an object to control all of my data so it can set things up in the background to it appears my tableviews load faster than they do now etc.
This is what I am trying to achieve.
I am setting a variable in the NSObject from the secondVC when the tableviewcell is selected like this:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
//Access selected cells content (cell.textLabel.text)
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
//Parent view logic (sends info back to the correct cell in parent view)
if (parentViewSelectedIndexPath.section == 0)
{
if (parentViewSelectedIndexPath.row == 0)
{
//Predicates restrict the values that will be returned from the query
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"%K like %#",#"MANUFACTURER",cell.textLabel.text];
NSArray *filterArray = [myDataArray filteredArrayUsingPredicate:predicate];
//[[self delegate] setManufactureSearchFields:filterArray withIndexPath:indexPath]; //This is where I pass the value back to the mainview
//Using Object
VehicleControllerNSObject *vehicleControllerNSObject = [[VehicleControllerNSObject alloc] init];
[vehicleControllerNSObject setFirstCell:filterArray];
}
//etc
At the end there you can see the method that is getting set up in the VechicleControllerNSObject which looks like this.
-(void)setFirstCell:(NSArray *)array{
manufactureSearchObjectStringFVC = [[array valueForKey:#"MANUFACTURER"] objectAtIndex:0];
NSLog(#"%#", manufactureSearchObjectStringFVC); // this prints the correct value to the console
}
As you can see this prints the correct output fine.
however I have no idea how to call manufactureSearchObjectStringFVC and pass the value it holds into the uitableviewcell that I would like to pass it in on my firstviewcontroller.
This is what I have for testing atm.
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
VehicleControllerNSObject *vehicleControllerNSObject = [[VehicleControllerNSObject alloc] init];
manufactureSearchObjectString = vehicleControllerNSObject.manufactureSearchObjectStringFVC;
NSLog(#"%#", vehicleControllerNSObject.manufactureSearchObjectStringFVC);
}
That nslog prints null..
I have three questions
1, how do I get the correct value into the first valuecontroller.
2, should I be using viewDidAppear like this?.. I think not.. how can I do this better
3, Do you think this is a good way of doing this type of thing, as in the future i would like to use the NSObjectClass to parse info, cache etc all behind the senses leaving the views to just display when the data is ready hopefully helping performance..
Any help would be hugely appreciated as I really want to learn this stuff as i know its important for me to know.
Your question is so beautifully and clearly formatted and diagrammed that it seems a shame to ask you to do a search. But here it is:
Search for Sharing Data between View Controllers
You'll find many good discussions about sharing data between view controllers.
Briefly, though, I can tell you why your code isn't working. In your tableView:didSelectRowAtIndexPath: method, you are creating (alloc/init) a new instance of your VehicleControllerNSObject class each time. Then back in your first view controller on viewDidAppear:, again you are creating (alloc/init) a whole new instance each time.
So you have multiple objects coming and going and they have nothing to do with each other. It's a bit like giving some important information to one person at a bus station and then later randomly picking some other person out and trying to retrieve that same information from her.
So one quick idea would be to create just once instance of your VehicleControllerNSObject (just an aside, that's a bit of a strange name for a class since generally all objective-c objects are descendants of NSObject anyway. I'm just going to call that VehicleController for now)
So let's say you wanted a 'sharedInstance' of VehicleController. You could add a class method to VehicleController to give you a way to easily get that one sharedInstance:
+(VehicleController*)sharedInstance {
static VehicleController *sharedInstance_ = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance_ = [[VehicleController alloc] init];
});
return sharedInstance_;
}
So to get that instance in methods in other classes you can just do something like :
VehicleController *sharedController = [VehicleController sharedInstance];
sharedController.someProperty = someValue;
// and then back in your first view controller, similarly:
VehicleController *sharedController = [VehicleController sharedInstance];
id someValue = sharedController.someProperty;
Again, check the search, many people have had good discussions on this. This is just one approach. I hope it at least makes sense why your code wasn't working.
Hope that helps.
To answer question 3. No.
I think that the best way to do something like this would be to use Core Data and it's NSManagedObject.
A combination of UITableViewController and NSFetchedResultsController that is feed from a Core Data sqlite backing store, if well set would feed and keep your UITableView updated.
It would be to long to describe all in here. So I will stop there.
If you don't want to go with that there is always the possibility to use a shared pointers to a mutable object or to use a singleton object to communicate information between UIViewController.

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 adding multiple annotations to map

Ok, so I’m having this problem. What I want to do is manually add multiple annotations to a map. When I add just one annotation, it works flawlessly. The pin drops, you can click on it to see its callout, life is good.
The problem comes when I want to add more than one. When I add the second, suddenly the pin’s aren’t coloured correctly (i.e. depending on their magnitude they should be a certain color, but they’re now both the same…), and more importantly when you click on them, to see their callout, the app crashes with exex_bad_access. I really have no idea what’s wrong, maybe I’m adding too many views to the map? But it’s only 9 pins and the pins themselves add just fine.
Here’s my code…
- (void)viewDidLoad {
[super viewDidLoad];
NSMutableArray *stops = [[NSMutableArray alloc] init]; //Get list of all the stops available
Bus *bus1 = [[Bus alloc] init]; // Bus 1 holds the stops
stops = [bus1 returnStops];
for (NSString *stop in stops) //Go through each stop to add annotation to map
{
Bus *bus2 = [bus1 initWithStop:stop]; //Create an instance of bus with a given stop
MapAnnotation *eqAnn = [MapAnnotation annotationWithBus:bus2];
[self.mapView addAnnotation:eqAnn]; //Add the annotation to the map
//[eqAnn release];
//[bus2 release];
}
[self recenterMap];
[stops release];
}
- (MKAnnotationView *)mapView:(MKMapView *)mapView
viewForAnnotation:(id <MKAnnotation>)annotation {
MKAnnotationView *view = nil;
if(annotation != mapView.userLocation) {
MapAnnotation *eqAnn = (MapAnnotation*)annotation;
view = [self.mapView dequeueReusableAnnotationViewWithIdentifier:#"busLoc"];
if(nil == view) {
view = [[[MKPinAnnotationView alloc] initWithAnnotation:eqAnn
reuseIdentifier:#"busLoc"] autorelease];
}
CGFloat magnituide = [eqAnn.bus.magnitude floatValue];
if(magnituide >= .80f) {
[(MKPinAnnotationView *)view setPinColor:MKPinAnnotationColorRed];
} else if(magnituide >= .60f) {
[(MKPinAnnotationView *)view setPinColor:MKPinAnnotationColorPurple];
} else
{
[(MKPinAnnotationView *)view setPinColor:MKPinAnnotationColorGreen];
}
[(MKPinAnnotationView *)view setAnimatesDrop:YES];
[view setCanShowCallout:YES];
}
return view;
}
even tried removing the second function, but it didn’t do anything.
Thanks for the help!
P.S I should also add, there’s usually one or two pins out of the 9 which works when you click the annotation…
If i even try to manually just two annotations by hand in the program (i.e., remove the loop), it still fails and the color is still wrong...
It would appear that your memory management of the stops variable is incorrect. You allocate a mutable array, then replace that array with the return value of -[Bus returnStops], then release that. Also it's not clear what's going on with bus2 - does -[Bus initWithStop:] return a different instance of Bus? It's not usual to send any method -init* on an already-initialised object. I think that you probably are confused by the memory management conventions in Cocoa Touch. Here's a collection of articles and other references on Cocoa memory management (which is the same beast).
Have you tried using AddAnnotations instead of add annotation? - (void)addAnnotations:(NSArray *)annotations. This might work for you...but looking at the answer above and further inspection you are having some memory managment issues in your viewDidLoad (though thi s might not be the cause of your problem, but it could be). First of you are allocating the array (stops) and then ovveriding it with some array in the Bus object, this will cause a leak. Also you are then releasing that array which might be causing the crash since you are releasing the array that is actually in the Bus object w ithout having increased a reference count to it. I am not sure what initWithStop is doing but you might be getting a leak here too if initWithStop retains the object.
I wouldn't call it a memory management problem -- I'd just say you are using array references incorrectly.
After constructing the array with NSMutableArray *stops = [[NSMutableArray alloc] init], the next step is to use [stops addObject: ] to add each stop you want to store.
After that? It's not clear what you are really trying to do.
SO the answer was that I kept sending bus1 the init object, so it got confused.
"Hi David,
Your data model looks hosed to me. You only have one bus object that you are repeatedly sending initWithStop: to.
Hope this helps.
Good luck!
"
Thank you guys for your help! You all helped me quite a bit!

Using the value of a string to determine the instance of a class

I have a switch statement similar to this one:
switch (number)
{
case 1:
if (imageView1.hidden == NO)
{
imageView1.hidden = YES;
} else
{
imageView1.hidden = NO;
}
break;
case 2:
if (imageView2.hidden == NO)
{
imageView2.hidden = YES;
} else
{
imageView2.hidden = NO;
}
break;
And so forth and so on.
My question is how do I use a string with a value say "imageView1" and use that to access the instance of my imageView class instead of having a different case for each instance of imageView? I know it muse be similar to creating an NSPath from a string or something like that, but I'm just not sure where to look or what it would be called.
Thanks in advance for any help!
I don't disagree with those who are concerned about the design, if this is actually the code. I will assume, however, that you are only posting a generalized version of your question. And since this is an important concept in Objective-C, so we should talk about it.
You can access an object's properties by name using Key Value coding, and the routine -valueWithKey:.
NSString *nameOfView = #"imageView1";
[[self valueForKey:nameOfView] setHidden:YES];
This will, in order, look for a method called -imageView1, an ivar named imageView1 and finally an ivar named _imageView1. This technique is very heavily used in Cocoa, and is important to understand. This is one of the many reasons we name things carefully, and yet another reason that we make accessors that handle memory management for us. Search the docs for "Key-Value Compliance" for more information.
Now for this specific case, I would tend towards something more like JimG's solution, using an NSArray of views, so I can loop through them and turn on or off the ones I want based on their index. But I can imagine a lot of cases where that wouldn't be appropriate, and KVC may be.
Why not put the instances in an NSArray and index into that?
NSArray *views = [NSArray arrayWithObjects: imageView1, imageView2, nil];
NSImageView *iview = [views objectAtIndex: number];
Also, you could consider something like:
iview.hidden = ! iview.hidden;
[Edit: missing asterisks, oops]