Creating a MKPolygon from user-placed annotations in map - iphone

I want the user to be able to create polygons after placing some (unknown number) MKpointAnnotations in the map.I have put a gesture recognizer that gets activated once the user taps a button, and so annotations are placed.But how to use these as corners for a MKPolygon?
Below the code for saving the corners of the polygon.This after some mods I did to it.Now the app crashes and the crash reporter says index out of range.The corners are MKPointAnnotation-s created via a GestureRecognizer.
-(IBAction)addCorner:(id)sender
{
NSMutableArray *addCorners = [[NSMutableArray alloc] init];
[addCorners addObject:pointAnnotation];
ptsArray = addCorners;
}
-(IBAction)addPolygonOverlay:(id)sender
{
int cornersNumber = sizeof(ptsArray);
MKMapPoint points[cornersNumber];
for (int i=0; i<cornersNumber; i++) {
points[i] = MKMapPointForCoordinate([[ptsArray objectAtIndex:i] coordinate]);
}
MKPolygon *polygon = [MKPolygon polygonWithPoints:points count:cornersNumber];
[mapview addOverlay:polygon];
}

First problem is the addCorner method. Instead of adding each corner to the ptsArray variable, it creates a new array with just the last corner and sets theptsArray equal to that so it only has the one, last corner.
Change the addCorner method like this:
-(IBAction)addCorner:(id)sender
{
if (ptsArray == nil)
{
self.ptsArray = [NSMutableArray array];
}
[ptsArray addObject:pointAnnotation];
}
Also make sure ptsArray is declared and synthesized properly:
//in the .h file...
#property (nonatomic, retain) NSMutableArray *ptsArray;
//in the .m file...
#synthesize ptsArray;
(By the way, why not add the corner to ptsArray right where the pointAnnotation is created instead of in a separate user action?)
Second problem is in the addPolygonOverlay method. You have to use the NSArray count property to get the number of items in the array. The sizeof function returns the number of bytes of physical memory the passed variable uses. For ptsArray which is a pointer, it will return 4. If the ptsArray has less than 4 items, you will get the "index out of range" exception.
So change
int cornersNumber = sizeof(ptsArray);
to
int cornersNumber = ptsArray.count;
Another important thing to note is that the polygon sides will be drawn in the order the points are in the array. If the user does not add corners in a clockwise or counter-clockwise order, the polygon will look strange. You could re-create the polygon overlay immediately after a user adds/removes an annotation so they get immediate feedback on how it will look.

Related

Can't quite get the randomization down

I have built a specialized card application. What it does is allow a user to 'draw' a card, then view the card, and place it back in a random place in the deck. The only problem that I am having is that, more often than not, the card is being placed at the top of the deck.
Here is the contents of the .h file:
#class _Card;
#interface _Deck : NSObject
#property (readonly) NSString *deckData;
#property (readonly) NSUInteger count;
#property (readonly) NSUInteger deckCount;
#property (readonly) BOOL needsReset;
#property (readonly) NSArray *cards;
- (id)initWithArray:(NSArray *)array;
- (id)initWithContentsOfFile:(NSString *)filePath;
- (void)shuffle;
- (NSArray *)draw;
- (void)reset;
- (void)changeEdition;
#end
Now, here is my draw method, which will draw a card (more than one if the cards so specify it) and then place that card back into the deck, if it is allowed:
- (NSArray *)draw {
// first, we create an array that can be returned, populated with the
// cards that we drew
NSMutableArray *drawArray = [[[NSMutableArray alloc] init] autorelease];
// next, we get the top card, which is actually located in the
// indexArray (I use this for shuffling, pulling cards, etc.)
NSNumber *index = [[[indexArray objectAtIndex:0] retain] autorelease];
// now we get the card that the index corresponds to
// from the cards array
_Card *card = [cards objectAtIndex:[index integerValue]];
// now I remove the index that we
// got from the indexArray...don't worry,
// it might be put back in
[indexArray removeObject:index];
// if the card is supposed to discard after
// draw, we leave it out
if(!card.discard) {
int insertIndex = arc4random_uniform(indexArray.count);
// then I insert the card into the deck using the random, random
// number
[indexArray insertObject:index atIndex:insertIndex];
}
_Card *cardCopy = [card copy];
// we add the card to the array
// that we will return
[drawArray addObject:cardCopy];
// next, if the card is not the final card...
if(!card.final) {
// ...and the card has an
// additional draw specified...
if(card.force) {
// we keep drawing until we need to stop
[drawArray addObjectsFromArray:[self draw]];
}
}
return drawArray;
}
Is there anything that I may be doing wrong? If you need any more information, please let me know. Thanks in advance for any help that you can provide.
If I am understanding this correctly, the problem is that it is inserting the card at index 0 in indexArray?
Okay, have you tried something like this:
(don't use this line yet [indexArray removeObject:index];)
if(!card.discard)
{
int insertIndex = arc4random_uniform(indexArray.count);
id obj = [indexArray objectAtIndex:index];
[indexArray removeObjectAtIndex:index];
[indexArray insertObject:obj atIndex:insertIndex];
NSLog(#"insertIndex is %i and obj is %#", insertIndex, obj);
}
else
{
[indexArray removeObjectAtIndex:index];
}
Your code seems to be okay, I'm guessing it just doesn't like removing the object before... I added the log just so you could see if it really was inserting it at the top each time. Give me an update- if this doesn't work, I could take a look at your project file.
What does "more often than not" mean?
What you show here looks perfect....
Remember that this is random and it is quite possible (although rare) to get a particular number 10 times in a row. Over the long run you should get an even distribution though.
Run this routine 10,000,000 times or so, and check the number of times that you get each number (make sure that you have the same number of cards in the deck each time) before deciding that something is wrong.
Also, are you sure that your indexArray contains the correct values and you aren't duplicating 0 in there?

access string names using variables in FOR statement

is is possible to access objects using variables in a FOR statement?
Say i have declared:
UIImageView *UIImageView0;
UIImageView *UIImageView1;
UIImageView *UIImageView2;
and i have 3 objects in an array and i call a FOR statement if x in the array is equal to 2 i want it to add the value of x to the UIImageView name like UIImageView1 etc
I have tried:
for (int x=0; x<[theArray count]; x++) {
UIImageView[x].image = etc....
}
but it gives me a error on UIImageView[x]
subscript requires size of interface 'UIImageView'
any ideas? or is it even possible with a UIImageView?
Thanks
You don't have three elements in an array, though; you have three independent variables with similar names. If you created an actual array, containing the values of the three variables, then you could use the for loop -- and in fact, the syntax would be just as you've shown (using the actual name of the array variable, of course.)
You could say
UIImageView * views[3] = {UIImageView0, UIImageView1, UIImageView2};
and then use, for example, views[i].image in your loop.
In order to put your UIImageView into an array you need to create an instance NSMutableArray (for instance)
The above code does not show any array, instead you have three ivars
UIImageView *UIImageView0;
UIImageView *UIImageView1;
UIImageView *UIImageView2;
and to access those you would use the name, not an array.
If you however put them into an NSMutableArray you can access them using
NSMutableArray array = [[NSMutableArray alloc]
initWithObjects:UIImageView0, UIImageView1, UIImageView2, nil];
[array objectAtIndex:i ]; // where i is 0,1 or 2
...
[array release];
You can put your UIImageView to another array, and get the UIImageView from that array.
NSArray *imagesArray = #[imageView0, ...]
for(size_t i = 0; i<theArray.count; i++)
{
UIImageView *imageView = [imagesArray objectAtIndex:i];
}
Or you can use the "tag" property of UIView like:
UIImageView *image0 = ...;
image0.tag = 100; //or 0 or something else
[self.view addSubview:image0];
And then get the UIImageView by tag in your FOR statement:
for(size_t i = 0; i<theArray.count; i++)
{
UIImageView *imageView = [self.view viewWithTag:100+i];
}

creating a Mutable array that can be added to in later clicks of the same button?

General noob questions:
(1) How can I create an NSMutable array in a buttonClicked action that I can add more entries to during subsequent clicks of the same button? I always seem to start over with a new array at every click (the array prints with only 1 entry which is the most recent button's tag in an NSLog statement).
I have about 100 buttons (one for each character in my string called "list") generated by a for-loop earlier in my code, and each has been assigned a tag. They are in a scrollview within the view of my ViewController.
I wish to keep track of how many (and which ones) of the buttons have been clicked with the option of removing those entries if they are clicked a second time.
This is what I have so far:
-(void) buttonClicked:(UIButton *)sender
NSMutableArray * theseButtonsHaveBeenClicked = [[NSMutableArray alloc] initWithCapacity: list.length];
NSNumber *sendNum = [NSNumber numberWithInt:sender.tag];
[theseButtonsHaveBeenClicked addObject:sendNum at index:sender.tag];
NSLog(#"%#",theseButtonsHaveBeenClicked);
}
(2) I have read that I may be able to use a plist dictionary but I don't really understand how I would accomplish that in code since I cant type out the items in the dictionary manually (since I don't know which buttons the user will click). Would this be easier if I somehow loaded and replaced the dictionary in a plist file? And how would I do that?
(3) I also have no idea how I should memory manage this since I need to keep updating the array. autorelease?
Thanks for any help you can provide!
Okay, firstly you are creating a locally scoped array that is being re-initialised on every call to buttonClicked:. The variable should be part of the class init cycle.
You will also be better off with an NSMutableDictionary instead of an NSMutableArray. With a dictionary we don't have to specify capacity and we can use the button's tags as dictionary keys.
Here's what you need to do, these three steps always go together: property/synthesize/release. A good one to remember.
//Add property declaration to .h file
#property (nonatomic, retain) NSMutableDictionary * theseButtonsHaveBeenClicked;
//Add the synthesize directive to the top of .m file
#synthesize theseButtonsHaveBeenClicked;
// Add release call to the dealloc method at the bottom of .m file
- (void) dealloc {
self.theseButtonsHaveBeenClicked = nil; // syntactically equiv to [theseButtonsHaveBeenClicked release] but also nulls the pointer
[super dealloc];
}
Next we create a storage object when the class instance is initialised. Add this to your class's init or viewDidLoad method.
self.theseButtonsHaveBeenClicked = [[NSMutableDictionary alloc] dictionary]; // convenience method for creating a dictionary
And your updated buttonClicked: method should look more like this.
-(void) buttonClicked:(UIButton *)sender {
NSNumber *senderTagAsNum = [NSNumber numberWithInt:sender.tag];
NSString *senderTagAsString = [[NSString alloc] initWithFormat:#"%#",senderTagAsNum];
// this block adds to dict on first click, removes if already in dict
if(![self.theseButtonsHaveBeenClicked objectForKey:senderTagAsString]) {
[self.theseButtonsHaveBeenClicked setValue:senderTagAsNum forKey:senderTagAsString];
} else {
[self.theseButtonsHaveBeenClicked removeObjectForKey:senderTagAsString]; }
[senderTagAsString release];
NSLog(#"%#", self.theseButtonsHaveBeenClicked);
}

UISlider how to set the initial value

I'm pretty new at this iphone dev stuff and i'm working on some existing code at a company. The uislider i'm trying to set the initial value on is actually in a UITableViewCell and is a custom control. I was thinking in the cell init
cell = (QuantitiesCell *)[self loadCellWithNibName:#"QuantitiesCell" forTableView:ltableView withStyle:UITableViewCellStyleDefault];
i could just call something like
((QuantitiesCell *)cell).value = 5;
The actual QuantitiesCell class has the member value and the following functions
-(void)initCell;{
if (listOfValues == nil) {
[self initListOfValues];
}
quantitiesSLider.maximumValue = [listOfValues count]-1;
quantitiesSLider.minimumValue = 0;
quantitiesSLider.value = self.value;
}
-(void)initListOfValues;{
listOfValues = [[NSMutableArray alloc] initWithCapacity:10];
int j =0;
for (float i = minValue; i <= maxValue+increment; i=i+increment) {
[listOfValues addObject:[NSNumber numberWithFloat: i]];
if (i == value) {
quantitiesSLider.value = j;
}
j++;
}
}
like i said, i'm pretty new at this so if i need to post more of the code to show whats going to get help, let me know,
This slider always is defaulting to 1, the slider ranges usually from 1-10, and i want to set it to a specific value of the item i'm trying to edit.
Setting slider.value is the correct way if your linkage is correct (you have made the connections in Interface Builder). If you have created a UISlider in code, all you have to do is set the value property. If that is not working then be sure firstly that the object is "live" (correctly allocated, not released, not out of scope etc.) and secondly that the functions in which you set slider.value are actually being called.
If you are using Interface Builder and are not sure of how to connect your slider as an IBOutlet, you should check out iTunes University - search for "CS193P" to find some excellent videos from Stanford University. The first couple will take you through making those connections. If using IB and you have not made the connection - nothing will happen.
PS I had the same issuer - you need to add the UISlide view first then you can change the value.

replaceObjectAtIndex array problems

Been searching for the answer to this for a while now and I think due to the nature of my array set up, I may be searching for the wrong answer!
I have a class which handles adding items to my array:
// Item.h
#interface Item : NSObject {
NSString *name;
NSNumber *seconds;
}
#property(nonatomic,copy) NSString *name;
#property(nonatomic,copy) NSNumber *seconds;
- (id)initWithName:(NSString *)n seconds:(NSNumber *)sec;
#end
and...
//item.m
#implementation Item
#synthesize name, seconds;
- (id)initWithName:(NSString *)n seconds:(NSNumber *)sec {
self.name = n;
self.seconds = sec;
return self;
}
#end
So to add an item, I use
Item *item1 = [[Item alloc] initWithName:#"runnerA" seconds:[NSNumber numberWithInt:780]];
I have some code which allows a user to edit a textfield (runner name) and the time which is a UIdatepicker set to hours and minutes. In the save method, that's working fine. It's the UPDATE that I cannot get to work. I've tried alsorts! Here's the code at the moment...
mainAppDelegate *appDelegate = (mainAppDelegate *)[[UIApplication sharedApplication] delegate];
Item *item = [[Item alloc] initWithName:inputName.text seconds:[NSNumber numberWithInt:secs]];
[appDelegate.arrItems replaceObjectAtIndex:rowBeingEdited withObject:item];
The above is simply adding a new item to the array (which is what I don't want). I'm not sure how to replace values. At the function, I have the row I need to update (rowBeingEdited) and the fields inputName.text and secs are both OK. (NSLog out confirms this).
How do I use the replaceObjectAtIndex to actually replace it with the values?! It's driving me mad now!!
Since you are simply trying to edit a particular row, why not use those property accessors that you already have set up in Item? It would look something like this:
Item *item = (Item *)[appDelegate.arrItems objectAtIndex:rowBeingEdited];
[item setName:inputName.text];
[item setSeconds:[NSNumber numberWithInt:secs]];
An a side note, are you using garbage collection, or do you manually release the Item objects that you create when adding items to the array? If you are doing it manually, it should look like this:
Item *item1 = [[Item alloc] initWithName:#"runnerA"
seconds:[NSNumber numberWithInt:780]];
[appDelegate.arrItems addObject:item1];
[item1 release];
This follows the rule of thumb: if you alloc, copy or retain anything, you must also release it. Note that this works because the array will retain the item when it is added.
Are you using NSArray or NSMutableArray?
Assuming you are using NSMutableArray, how did you initialize and populate the array in the first place?
For example, it's not enough to use -initWithCapacity: or +arrayWithCapacity: which only sets aside space. You have to use -addObject: for the first round of population, before you can use -replaceObjectAtIndex:withObject::
Note that NSArray objects are not like C arrays. That is, even though you specify a size when you create an array, the specified size is regarded as a “hint”; the actual size of the array is still 0. This means that you cannot insert an object at an index greater than the current count of an array. For example, if an array contains two objects, its size is 2, so you can add objects at indices 0, 1, or 2. Index 3 is illegal and out of bounds; if you try to add an object at index 3 (when the size of the array is 2), NSMutableArray raises an exception.