Adding an unknown number of annotations to MKMapView - iphone

So I am using the map to add points for each person that has an address in the contacts, and I am having a bit of a hard time figuring out how to set it up for the unknown number of contacts. right now the way I have it set up, it is only adding a pin for the last contact in the list. here is some code: address is a string that is set from addressProperty.
if(addressProperty != nil)
{
[location geocodeAddressString:address completionHandler:^(NSArray *placemarks, NSError *error) {
NSMutableArray *array = [[NSMutableArray alloc]init];
for(int a = 0; a < [placemarks count]; a++)
{
self.placeMarkData = [placemarks objectAtIndex:a];
[point setCoordinate: self.placeMarkData.location.coordinate];
[array addObject:point];
pin.animatesDrop = YES;
point.title = address;
[map addAnnotations:array];
}
}];
}
So when I run the app I can see the pin being set in each location and moving to the next location until it ends on the last locations in the list. How can I add a point for each? I am sure it is an easy solution, but it is eluding me right now.

There must be a loop around all of this to be going through all the contacts. Is location a CLGeocoder? You shouldn't set two geocodeAddressString functions going on the same geocoder, you'll need to initialize a new one for each geocode your doing (unless you know for sure the previous geocode, which is asynchronous, has finished).
Four other problems:
The point variable is the same one each time. As you change the coordinate you'll that point appear in different places on the map, but it's the same one so you'll never see more than on on the map at any time.
Because it is running asynchronously you should be writing and reading a variable outside the block. Right now two geocodes could get responses at the same time and one sets self.placeMarkData just as the other one is reading the location.coordinate from it.
What is pin used for?
Don't bother adding array to the map until you've finished filling it up. If you want the points on screen asap then add them individually to the map. Adding the entire array, then adding another thing to the array and adding the entire array again is a waste.

Seems like a simple bug in your code. Can you try moving the [map addAnnotations:array]; line outside of the for loop?

Related

iOS MapKit show nearest annotations within certain distance

Currently i am working on a Location based application for iPhone/iPad . I have several annotations in my MapKit , what i want to do is to track the location of the user and shows the annotations that are within the 3km . Can somebody give me a start ?
Sorry for the delayed response... the question just fell off my radar.
I'm going to suppose that you have a method that returns a set of NSValue-wrapped CLLocationCoordinate2D structs (the basic approach is the same regardless of what your internal data representations are). You can then filter the list using a method something akin to the following (warning: typed in browser):
NSSet *locations = ...;
CLLocation centerLocation = ...; // Reference location for comparison, maybe from CLLocationManager
CLLocationDistance radius = 3000.; // Radius in meters
NSSet *nearbyLocations = [locations objectsPassingTest:^(id obj, BOOL *stop) {
CLLocationCoordinate2D testCoordinate;
[obj getValue:&testCoordinate];
CLLocation *testLocation = [[CLLocation alloc] initWithLatitude:testCoordinate.latitude
longitude:testCoordinate.longitude];
BOOL returnValue = ([centerLocation distanceFromLocation:testLocation] <= radius);
[testLocation release];
return returnValue;
}
];
With the filtered set of coordinates in hand, you can create MKAnnotation instances and add them to the map in the usual manner, as described in Apple's documentation.
If you have many thousands of test locations then I suppose this approach could start to incur performance issues. You would then want to switch your point storage approach to use, e.g., quadtrees, to reduce the number of points that need to be precision-filtered. But don't optimize prematurely!
Hope that helps!

How to convert NSNumbers into integers from an NSArray(iPhone)

I have a a game for the iphone where the tags of 32 buttons are vital. I am trying to make a feature where it would save these 32 integers into an array so you can play a different game on the board with the same integers(tags) and then later come back to your game with the integers in the same way.(Not asking about multitasking or anything like that) There are some questions like this but none of their answers seem to work.
Right now I have:
savedGame = [NSArray arrayWithObjects: [NSNumber numberWithInt:a2.tag],
[NSNumber numberWithInt:a4.tag],
[NSNumber numberWithInt:a6.tag],
[NSNumber numberWithInt:a8.tag],
[NSNumber numberWithInt:b1.tag],
[NSNumber numberWithInt:b3.tag],
[NSNumber numberWithInt:b5.tag],
[NSNumber numberWithInt:b7.tag],
[NSNumber numberWithInt:c2.tag],
[NSNumber numberWithInt:c4.tag],
all the way to:
[NSNumber numberWithInt:h7.tag],nil];
for the part where you save the game
and:
a2.tag = [((NSNumber*)[savedGame objectAtIndex:0]) intValue];
all the way up to:
h7.tag = [((NSNumber*)[savedGame objectAtIndex:32]) intValue];
at the part where you resume your game. However, this code is apparently SO BAD that whenever it gets to this code in the ios simulator it crashes XCODE TOO(which gets very annoying because I need to force quit and start the program again)(its xcode 4)
Saved game is created in the .h like:
NSArray *savedGame;
I guess I will have to add something where it checks if there are 32 numbers in that second part--so if you could add that in your answer that would be great--and thanks in advance! I think the problem is with the second part of that code-- the
g2.tag = [((NSNumber*)[savedGame objectAtIndex:25]) intValue];
I have also heard of NS Mutable Arrays, so maybe that could solve my problem?
Thanks for any and all help!! And I am also open to new ways--maybe not arrays--that could solve this problem!! Thankyou in advance!!!
Use an NSMutableArray instead and you can do things like
[savedGame replaceObjectAtIndex:4 withObject:[NSNumber numberWithInt:a8.tag]];
You can also save these in NSUserDefaults:
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
[prefs setInteger:a4.tag forKey#"a4.tag"];
a4.tag = [prefs integerForKey:#"a4.tag"];
This latter approach will also allow you to use a for loop to set and retrieve these values.
You should really use an NSDictionary for this job.
http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSDictionary_Class/Reference/Reference.html
This will allow you to query your data using keys.
If you are suspecting that you could be accessing your array beyond its size, then you could check if the array has got the number of objects you expect by inspecting [savedGames count].
Don't know if it helps.
As a general suggestion, I would first run your unmodified program in debugging mode (if you are not doing that already), and possibly step-by-step in the region where it fails. So you will know exactly which is the offending statement.
In order to further try and understand what is happening, I would split your assignments in multiple lines:
NSNumber* number = [savedGame objectAtIndex:0];
a2.tag = [number intValue];
if you set a breakpoint on the first statement you can see what objectAtIndex is returning.
Of course I understand it is big work to make all those changes.
As an alternative approach, since it seems to me that what matters to you are the ints (not the NSNumbers, you are using those only to store the ints somewhere), you could simply resort to using a C array of longs. This would avoid all the conversions back and forth. Of course, I am not sure whether you are really only interested in the ints.
Ummmm, based on the way your tags are situated, i guess its some kind of checkers like game? As in you have a 64 square grid but only half of the spaces are playable.
As for your comment about a mutable array, a mutable array just allows you to dynamically add or remove objects to the array. A regular NSArray has a fixed size and components (you cant add to it or remove from it). So you dont need to do the initWithObjects: part with a mutable array.
But, I really think you should restructure the way you are doing this. In this situation, you would want an object that has a location. Then you can just do
#interface GamePiece : NSObject
{
CGPoint location; //an object with an x and y
}
#property (assign, readwrite) CGPoint location;
-(void)saveTheGame {
[self setSavedGame:[[[NSMutableArray alloc] init] autorelease]];
for(GamePiece *piece in allPieces)
{
[savedGame addObject:piece]
}
}
Then in loadGame
if([savedGame count] == 32)
{
[self setAllPieces:nil]; //allPieces is an array object of a "Game" that contains
//references to all the pieces on the board
for(GamePiece *piece in savedGame)
{
[allPieces addObject:piece];
}
}

I'm having issues inputing a name if applicable

I have a function here that upon completing a single round, if your score is higher than either a default score entry or a newly placed high score then it will swap its data with your data and push everything else down. removing the last entry from the list. currently this is just one exchange and for functions sake I'm going to hard code it and then refactor it later.
My main problem is that when I set up a text input view to capture the players name execution continues immediately without the players input and crashes the game. I commented out the line that sets the text because I have a default value in place just in case any attempt that I try to make fails. How can I get Execution to wait for a moment while input is taken? Would I have to set up a delegate method? If so I'm still a bit confused by delegates. I could set it up to work but I don't understand it, so I wouldn't be able to do any other special custom tasks with it. I've worked on it for a while and got no further...
-(void)saveData:(ScoreKeep *)stats{
NSMutableDictionary *swap = [[NSMutableDictionary alloc]init];//used for swaping entries
NSString *filePath = [self pathOfFile];
NSLog(#"Writing to %#", filePath);
if ([[NSFileManager defaultManager]fileExistsAtPath:filePath]) {
NSLog(#"Loading previous dictionary to save...");
dataDictionary = [NSMutableDictionary dictionaryWithContentsOfFile:filePath];
if ([dataDictionary objectForKey:#"1"]) {
NSMutableDictionary *highScore = [dataDictionary objectForKey:#"1"];
if ([stats.score intValue] > [[highScore objectForKey:#"SCORE"] intValue]) {
NSLog(#"You Win! score: %# highscore: %#", stats.score,[NSNumber numberWithInt:[[highScore objectForKey:#"SCORE"] intValue]] );
stats = [[ScoreKeep alloc] initWithNibName:#"Scorekeep" bundle:nil];
NSLog(#"Setting up name entry");
[self.view addSubview:stats.view]; //New view is added so that the player can input data(Assume it is complete);
//stats.nameTag = setName.nameTag;//This line is executed before the new view is dismissed causing an error to occur
[stats setupDictionary]; // It just goes down hill from here if the previous line is uncommented
[dataDictionary setObject:stats.sComponents forKey:#"1"];
}else {
NSLog(#"You Lose: %# highscore: %#", stats.score,[NSNumber numberWithInt:[[highScore objectForKey:#"SCORE"] intValue]] );
}
NSLog(#"Got first place entry");
}else {
NSLog(#"Initilizing Score");
}
}else{
NSLog(#"Creating new dictionary to save...");
dataDictionary = [[NSMutableDictionary alloc]init];
}
[dataDictionary writeToFile:filePath atomically:YES];
}
Help would greatly be appreciated. If more information is needed I'd be happy to provide.
by the way ScoreKeep is an object that contains a dictionary and a function to create a dictionary such that it can set any values I need and package them into sComponents(the dictionary to be entered into the main savefile)
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#class omphalosUtility;
#pragma mark -
#pragma mark Saving data
#pragma mark -
static inline void poop(){
NSLog(#"POOP");
}
I'm going to try making a utility file that works independently of the app so that I Can update files and perform other universal operations such as saving when needed. Its a step in a direction that i'd like to take.
If i get it right, (The code is really nasty, man...) your problem is that you are trying to present a View Controller with the wrong way.
Correct me if i'm wrong, is ScoreKeep is a ViewController? if so, you have to name it properly. that's for a start.
Second, you cant present another view controller only by adding its "view" property to the current view controller's View Hierarchy. that way the view will not respond properly to the events.
the correct way to to what you'r trying to do is by presenting the ScoreKeep ViewController modally.
there is no other right way to do this without using delegation. you will have to acquire this technique.
Your view controller that responsible for getting the name from the user need to have a way to tell it's master view controller that the user entered a name. and that is achieved through delegation.
What you should do:
Basically you create a protocol called something like "NamePrompterViewControllerDelegate"
that will have at least one method that will be called when the user will done entering his name.
Your ScoreKeepViewController should have an instance variable that implemented the protocol (Look at the apple documentation on protocols for assistance)
Your main view controller (the one that contains the method you added) then should implement the protocol you created, and set itself as the delegate of ScoreKeep like that:
stats = [[ScoreKeep alloc] initWithNibName:#"Scorekeep" bundle:nil];
stats.delegate = self;
For more info on presenting and dismissing ViewControllers modally you should read the documentation at Apple Documentation
I hope i helped you, there is just a lot to cover and it hardly can be done by writing an answer.
Feel free to ask more for clearance.

How do I determine if a coordinate is in the currently visible map region?

I have a list of several hundred locations and only want to display an MKPinAnnotation for those locations currently on the screen. The screen starts with the user's current location within a 2-mile radius. Of course, the user can scroll, and zoom on the screen. Right now, I wait for a map update event, then loop through my location list, and check the coordinates like this:
-(void)mapViewDidFinishLoadingMap:(MKMapView *)mapView {
CGPoint point;
CLLocationCoordinate2D coordinate;
. . .
/* in location loop */
coordinate.latitude = [nextLocation getLatitude];
coordinate.longitude = [nextLocation getLongitude];
/* Determine if point is in view. Is there a better way then this? */
point = [mapView convertCoordinate:coordinate toPointToView:nil];
if( (point.x > 0) && (point.y>0) ) {
/* Add coordinate to array that is later added to mapView */
}
So I am asking to convert the coordinate where the point would be on the screen(unless I misunderstand this method which is very possible). If the coordinate isn't on the screen, then I never add it to the mapView.
So my question is, is this the correct way to determine if a location's lat/long would appear in the current view and should be added to the mapView? Or should I be doing this in a different way?
In your code, you should pass a view for the toPointToView: option. I gave it my mapView. You have to specify an upper bound for the x and y too.
Here's some code which worked for me (told me the currently visible annotations on my map, while looping through the annotation):
for (Shop *shop in self.shops) {
ShopAnnotation *ann = [ShopAnnotation annotationWithShop:shop];
[self.mapView addAnnotation:ann];
CGPoint annPoint = [self.mapView convertCoordinate:ann.coordinate
toPointToView:self.mapView];
if (annPoint.x > 0.0 && annPoint.y > 0.0 &&
annPoint.x < self.mapView.frame.size.width &&
annPoint.y < self.mapView.frame.size.height) {
NSLog(#"%# Coordinate: %f %f", ann.title, annPoint.x, annPoint.y);
}
}
I know this is an old thread, not sure what was available back then... But you should rather do:
// -- Your previous code and CLLocationCoordinate2D init --
MKMapRect visibleRect = [mapView visibleMapRect];
if(MKMapRectContainsPoint(visibleRect, MKMapPointForCoordinate(coordinate))) {
// Do your stuff
}
No need to convert back to the screen space.
Also I am not sure the reason why you are trying to do this, I think this is strange to not add annotations when they are not on the screen... MapKit already optimizes this and only creates (and recycles) annotation views that are visible.
After a little bit of reading I can't find anything that says this is a bad idea. I've done a bit of testing in my app and I always get correct results. The app loads much quicker when I only add coordinates that will show up in the currently visible map region instead of all the 300+ coordinates at once.
What I was looking for was a method like [mapView isCoordinateInVisibleRegion:myCoordinate], but so far this method is quick and seems accurate.
I've also changed the title to read "in the visible map region" instead of the previous because I think the incorrect title may have confused my meaning.

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!