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?
Related
I have been debating between a .plist and a sqlite3 database to hold some data that I need to access, but not manipulate within the .plist/database. Here is my question. Lets say I want to store the height and color of trees, flowers, bushes and I want each piece of information to be accessible. Below is similar to what I would like:Trees Palm 6 feet Green Willow 8 feet Brown Bushes Evergreen 5 feet Green Cinquefoil 2 feet Yellow Flowers Rose 1 foot Red Tulips 2 feet Yellow
So if I want to individually access the 2 feet height under Tulips and display it in a text box in my app..what is the best form of data store/resource to use. .plist or sqlite? How would I lay this out as a .plist?
As always, I appreciate your time and efforts!!
Thanks!
Since you dont have much data just use a .plist it would be easier to manage
make each parent Trees,Flowers,Bushes and array make each child item a dictinary , so when you check if a child is satisfies your requirement like 2 feet height under Tulips use it.
create some plist like this:
Code Sample:
Note:I didnt test this you need to improve this
you can use some kind of logic here like this to check the color,kind or height.
I am giving an example from my project for you to see how you would filter the plist, so change the name of the function as you wish.
I won't change function names cause "nobody aint got time for that" :)
create a nsobject class called ParseSchedulePlist
in ParseSchedulePlist .h
#import <Foundation/Foundation.h>
#interface ParseSchedulePlist : NSObject
#property (nonatomic,strong) NSMutableDictionary * agendaDic;
- (void)passStringToAgendaDic:(NSString *)string;
//trees
-(NSArray*)getTrees;
-(NSDictionary*)getItems:(NSInteger ) section ;
-(NSString*)getItemKind :(NSInteger ) section;
-(NSString*)getItemColor :(NSInteger ) section;
-(NSNumber*)getItemheight :(NSInteger ) section;
//flowers
//do the same of above for flowers an bushes took
#end
in ParseSchedulePlist .m
#import "ParseSchedulePlist.h"
#implementation ParseSchedulePlist
#synthesize agendaDic = _agendaDic;
- (void)passStringToAgendaDic:(NSString *)string {
//get plist from bundle
NSString * path = [[NSBundle mainBundle] pathForResource:string ofType:#".plist"];
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:path];
NSLog(fileExists ? #"Yes" : #"No");
NSLog(#"Path is %#",path);
NSLog(#"agendaDic is %u",[self.agendaDic count]);
self.agendaDic = [NSMutableDictionary dictionaryWithContentsOfFile:path];
}
-(NSArray*)getTrees
{
NSArray * value = [[self agendaDic] objectForKey:#"trees"];
return value;
}
-(NSDictionary*)getItems:(NSInteger ) section
{
NSDictionary * value =[[self getTrees] objectAtIndex:section];
return value;
}
-(NSString*)getItemKind :(NSInteger ) section;
{
NSString * value =[[[self getItems] objectAtIndex:section] objectForKey:#"kind"];
return value;
}
-(NSString*)getItemColor :(NSInteger ) section
{
NSString * value =[[[self getItems] objectAtIndex:section] objectForKey:#"color"];
return value;
}
-(NSNumber *)getItemheight :(NSInteger ) section;
{
NSNumber * value =[[[self getItems] objectAtIndex:section] objectForKey:#"height"];
return value;
}
//write the same functions for flowers and bushes
#end
in your regular view controller .h:
#import "ParseSchedulePlist .h"
#property (nonatomic, strong) ParseSchedulePlist *agenda;
in your regular view controller .m:
#import "ParseSchedulePlist .h"
#synthesize agenda;
//here calls for the special class
self.agenda=[[ParseSchedulePlist alloc] init];
[self.agenda passStringToAgendaDic:#"name of the plist"];
NSMutableArray *newArray=[ NSMutableArray alloc] init];
//here for example get all the palm trees under 6 feet
for(int i=0; i<[self.agenda getTrees] count] i++)
{
if([self.agenda getItemKind :i] isquealtostring #"palm"){
if([self.agenda getItemheight :i] <= 6)
[newArray add object:[self.agenda getItems:i];
}
}
Nslog(#"print your array %#",newArray);
You can achieve the same with core data. Core data as such would be a little difficult to use. I have always used it with Magical Record, a wonderful library.
It's very easy to map objects with relation. In your case there is one to many and one to one relation between parent and child.
With MagicalRecord if you wanted to find an item with height equal to 2. You can do this in two simple steps
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"height =%#",#2];
NSArray *items = [Item findAllWithPredicate:predicate];
Good thing is that since there is a one to one relationship from an item to group, each item will have info about group. So everything nicely fits and it's easier to search what ever way you like.
You can find the source code for the details.
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.
I've been trying to figure out how to have a UIImageView with a next and previous button which when clicked will go to the next or previous image in the array. So far, here's what I have.
In my .h file I have declared:
IBOutlet UIImageView *imageView;
NSArray *imageArray;
And I also added:
-(IBAction) next;
In the .m file I have:
-(void) viewDidLoad;
{
imageArray = [[NSArray arrayWithObjects:
[UIImage imageNamed:#"1.png"],
[UIImage imageNamed:#"2.png"],
nil] retain];
}
Now here is where I'm struggling. I have the IBAction defined as follows in my .m:
-(IBAction)next
{
if (currentImage + 1 == [imageArray count])
{
currentImage = 0;
}
UIImage *img = [imageArray objectAtIndex:currentImage];
[imageView setImage:img];
currentImage++;
}
My problem is that I do not know where to define the currentImage index integer or how to define it. Is it in the header? The implementation? And how exactly do I declare it?
To be honest I'm not even 100% sure the code I currently have is right, although I think if I can define the index, it will work.
Any advice?
This is how I would do it. (I've changed the name of a few instance variables: currentImage sounds like it could be a pointer to an image (UIImage *) rather than just an integer value; adding Index to the end makes that more clear. It may be obvious now that it's an integer, but when you revisit this code (or other code you write) in a month, it may be less obvious; or maybe that's just me)...
MDSlideshowController.h:
#import <UIKit/UIKit.h>
#class MDBilboBaggins;
#interface MDSlideshowController : NSObject {
IBOutlet UIImageView *imageView;
NSArray *imageArray;
NSUInteger currentImageIndex;
BOOL someOtherVariable;
MDBilboBaggins *bilboBaggins;
// keep adding more instance variables as needed
}
- (IBAction)previous:(id)sender;
- (IBAction)next:(id)sender;
#end
MDSlideshowController.m:
#import "MDSlideshowController.h"
#import "MDBilboBaggins.h"
// you could perhaps define currentImageIndex here, but see notes below:
// NSUInteger currentImageIndex = 0;
#implementation MDSlideshowController
// `currentImageIndex` is automatically initialized to 0 during init
// `someOtherVariable` is automatically initialized to 0 (NO) during init
-(void) viewDidLoad
{
imageArray = [[NSArray arrayWithObjects:
[UIImage imageNamed:#"1.png"],
[UIImage imageNamed:#"2.png"],
nil] retain];
[imageView setImage:[imageArray
objectAtIndex:currentImageIndex]];
}
- (IBAction)previous:(id)sender {
if (currentImageIndex == 0) {
currentImageIndex = [imageArray count] - 1;
} else {
currentImageIndex--;
}
[imageView setImage:[imageArray objectAtIndex:currentImageIndex]];
}
- (IBAction)next:(id)sender {
if (currentImageIndex + 1 >= [imageArray count]) {
currentImageIndex = 0;
} else {
currentImageIndex++;
}
[imageView setImage:[imageArray objectAtIndex:currentImageIndex]];
}
#end
Basically, you put instance variables right underneath the ones you've already defined. They can be of almost any type. You can use the types Cocoa Touch knows about, or classes you make yourself. In this example, I said that there was a special class named MDBilboBaggins by using the #class MDBilboBaggins statement. Then, I add the #import "MDBilboBaggins.h" part in the .m file: this can help speed up compile times.
As I mentioned in the comment, you could perhaps define the currentImageIndex variable inside the .m file, however, it would be a static variable that is common to, and shared, by all instances of the class. This can be useful in some situations, but create issues in others. For example, imagine we have 2 slideshow controllers, each with different images created and showing slides in 2 different windows. If they were modifying a shared currentImageIndex variable, they'd mess each other up if you would switch between them and start clicking Previous and Next indiscriminately. That's why in this case it might make more sense to just make it an instance variable by defining it other your other instance variables in the .h file.
[EDIT] Regarding the :(id)sender parameter: in this example, it wasn't used at all, I generally do it out of habit, since in some circumstances, it can save a lot of code, and simplify things dramatically. For example, say you had 9 different buttons and you wanted each button to load a specific image (or perform a specific operation). Now, you could define 9 separate methods like - (IBAction)button1Clicked;, or you could do it the easy way and just define a single - (IBAction)loadImage:(id)sender method. In your nib file, you would give each button a different tag (an NSInteger value), like 1 − 9. Then in your single method you could do this:
- (IBAction)loadImage:(id)sender {
NSInteger buttonTag = [(NSButton *)sender tag];
[imageView setImage:[UIImage imageNamed:
[NSString stringWithFormat:#"image%ld.png",buttonTag]]];
}
In this case, sender is the object that sends the message, which would be the button that was clicked on. Though admittedly a contrived example, by providing that one additional parameter, I probably saved 100 lines of needless code and complexity of having 9 separate methods.
[EDIT #2] Replaced the one pseudo-coded (written in Safari) next: method with actual code from one of my apps that I know works.
Hope this helps...
you need to declare the currentindex in the header like so:
NSInteger currentImage;
This way the value is saved throughout the views lifetime
Hopefully I can make this clear, but I am new to Objective-C and to be honest not great with Arrays in the first place.
So, I have a Singleton class (called SingletonState) that I am using to pass variables around my app (please can we leave the whether I should use Singleton classes out of this - I will fix that later). In this class, I have an NSMutableArray (called arrayMyEvents). I have also created a class that I am going to store a list of events (called EventClass). When the user logs in, I call a web service and get back 3 strings. The 3rd string is a comma separated list of value. I parse the data and populate the custom class EventClass. I then add this class to the SingletonState.arrayMyEvents.
I have all of this working. I can go to another ViewController and access the "count" of items in arrayMyEvents.
PROBLEM: I now want to edit one of the ScheduledEventsClass"es" in my array. How do I get access to it and edit some of the properties and update the one in my SingletonState class?
Here is some of the code, that I've tried:
NSString *sWebServiceEvents = [[NSString alloc] initWithFormat:#"%#", [result objectAtIndex:2]];
if ( [ sWebServiceEvents isEqualToString:#"NULL" ] != true ) {
NSArray *arrayEvents = [sWebServiceEvents componentsSeparatedByString:#","];
// If the array has not been initialized they initialize it.
if (sharedState.arrayMyEvents == nil) {
sharedState.arrayMyEvents = [[NSMutableArray alloc ] init ];
}
for (NSString * sEvent in arrayEvents) {
// Set equal to the value of the array (the Event Number) at the same
// position as the row that we are being asked to return a cell/row for.
EventClass *eventClass = [[EventClass alloc] retain];
eventClass.sEvent = sEvent;
[ sharedState.arrayEvents addObject:eventClass ];
}
NSLog(#"LoginView - sharedState.arrayMyEvents Count: %d", [sharedState.arrayMyEvents count]);
}
Here is me trying to access it in another ViewController:
EventClass *eventClass =
[sharedState.arrayMyEvents objectAtIndex:row ];
NSLog(#"eventClass.sEventNumber: ", eventClass.sEventNumber);
eventClass.sLocation = #"Jason's Big Location";
You're going to have some memory leaks from the sEvent loop. [[EventClass alloc]retain] leaves you an uninitialized EventClass object with a reference count of 2. You'll need to change that to [[[EventClass alloc] init] autorelease] to keep it from leaking. The arrayEvents NSMutableArray will retain it during the addObject: call. (Shouldn't that be [sharedState.arrayMyEvents addObject: eventClass] in the loop?)
After that, all you have to do to edit the EventClass object in the second block of code is edit it. The eventClass variable is a pointer to an object in the array. Anything done to that object doesn't affect the pointer referencing it, it affects data referenced by it. The code you have in the second block should change the sLocation of the selected object as you intend.
You have a few more memory leaks in there, too. Use Cmd-Shift-A to build with the static analyzer and it'll tell you where.
Maybe the problem is that you put them in sharedState.arrayEvents but try to take them out of sharedState.arrayMyEvents. Different variables.
Also, lots of memory leaks.
Thanks John and St3fan, your answers and time are appreciated!!!
I think that I figured out my issue:
Basically, the class that I created (EventClass) had the properties setup like this:
#property (nonatomic, assign) NSString *sStudyNumber;
#property (nonatomic, assign) NSString *sTheater;
but, they should be (or at least I got it to work like this):
#property (nonatomic, retain) NSString *sStudyNumber;
#property (nonatomic, retain) NSString *sTheater;
Then, in my second view I was able to do this:
EventClass *eventClass = [sharedState.arrayMyEvents objectAtIndex:row ];
NSLog(#"MyEvents: %#", eventClass.sEventNumber);
eventClass.sLocation = #"Jason's Big Location";
I then checked it in another method of the view using this and it was still there:
EventClass *eventClass = [sharedState.arrayMyEvents objectAtIndex:row ];
NSLog(#"MyEvents: %#", eventClass.sEventNumber);
NSLog(#"MyEvents: %#", eventClass.sLocation);
I also, checked it in yet another view and the value was maintained in the SharedState.arrayMyEvents without issue. :)
In the end, I believe that I boiled down to the difference between "assign" and "retain".
Now, on to the memory leaks :(
Please, let me know if you see any other issues with this.
Thanks,
Jason
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.