Can I have 2 arrays within a plist? - iphone

I am trying to save data for an iphone app that I am building. My data structure is to save a gamesArray that contains individual gameArray. In each gameArray I have a date, name, home/away, and a shotArray. The shotArray contains the shots. The shots when loaded create UIButtons to display on the screen. The shotButtonArray contains these buttons. When I view my file in the plist editor I don't see distinct arrays. I only see 1 array and all of the created objects are just listed. Why does it only show one array. Here is how I am saving my data
for (UIButton *button in shotButtonArray) {
assert([button isKindOfClass:[UIButton class]]);
[button removeFromSuperview];
}
for (Shot *shot in shotArray) {
[shot subtract_miss_And_make];
}
[self reDrawShots];
[shotButtonArray removeAllObjects];
[shotArray removeAllObjects];
[gameArray removeAllObjects];
[gameArray addObject:theDate];
[gameArray addObject:theName];
NSNumber *theNumber = [NSNumber numberWithInt:homeAway];
[gameArray addObject:theNumber];
[gameArray addObject:shotArray];
gameNumber = 0;
[gamesArray addObject:gameArray];
NSString *path = [self findGamesPath];
[NSKeyedArchiver archiveRootObject:gamesArray toFile:path];
Am I saving my data incorrectly. Why does it show only one array.

There is nothing technically wrong with the way you are saving it. I am confused though; why are you removing all the objects from shotArray before saving it to the gameArray? Also, why are you storing only one gameArray within the gamesArray?
Even though there is nothing wrong with the way you are saving this, I will suggest a better way to do this. You should create a new class that represents a "game". This can save the attributes you want like the date, name, number, etc. using keys and values. If you override the -(void)encodeWithCoder:(NSCoder*)coder function in that class you can call:
[coder encodeObject:theData forKey:#"DATE"];
[coder encodeInteger:theNumber forKey:#"NUMBER"];
Then, when you override - (void)encodeWithCoder:(NSCoder *)encoder you can access that value be calling:
theDate = [[coder decodeObjectForKey:#"DATE"] retain];
theNumber = [coder decodeObjectForKey:#"NUMBER"];
This way, you can just create an array of these objects, ask NSKeyedArchiver to archive the array as its root object, and everything will be handled cleanly within the "game" class. If you ever want to store other more complicated values, you can make additional classes and use the encodeObject and decodeObject functions.
You can look here for more info:
http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Protocols/NSCoding_Protocol/Reference/Reference.html

Related

Navigating plists and settings objects

I have a resource file (plist) added to my project. I am currently coding some sort of a "helper" for reading and writing to it. I have 3 get and 3 set methods. First one returns an object at the top, second one returns object which is inside of another dictionary (see code) and the third one returns an object at any given depth I just have to specify the node names so it can get there. (I hope you can understand me)
The problem comes with setters. Setting an "surface" object is no big deal so is setting an object that is in another dictionary. The problem comes when I try to set an object at a depth.
Before I write anything else I will post the code so you can understand what I'm saying.
fullContent is a NSMutableDictionary containing the file.
//This one is easy, just return the object for the key.
- (id)getSurfaceObjectForKey:(NSString*)key
{
return [fullContent objectForKey:key];
}
//Hope you understand this one. Main parent is a string with the name of the first node. It gets a dictionary out of my plist and returns an object for key (I have a dictionary structured plist)
- (id)getMainParentChildObjectForKey:(NSString*)key
{
NSAssert(!mainParent, #"Main parent must not be nil");
return [[fullContent objectForKey:mainParent] objectForKey:key];
}
//This one gets the element at any given depth I just have to pass in an array containing node names
- (id)getObjectForKey:(NSString *)key atDepthWithChildren:(NSArray *)children
{
id depthElement = fullContent;
for (int i = 0; i < children.count; i++)
depthElement = [depthElement objectForKey:[children objectAtIndex:i]];
return [depthElement objectForKey:key];
}
//Sets a top (surface) object
- (void)setSurfaceObject:(id)object ForKey:(NSString *)key
{
[fullContent setObject:object forKey:key];
[self writePlistContent];
}
//Sets an object inside a dictionary (mainParent - string with the name of dictionary node)
- (void)setMainParentChildObject:(id)object forKey:(NSString *)key
{
[[fullContent objectForKey:mainParent] setObject:object forKey:key];
[self writePlistContent]; //Self explanatory. I write this to file
}
//This is where my problem comes. How do I save this to plist without making any other changes to it? Im guessing I have to rebuild it from inside up?
- (void)setObject:(id)object forKey:(NSString *)key atDepthWithChildren:(NSArray *)children
{
id depthElement = fullContent;
for (int i = 0; i < children.count; i++)
depthElement = [depthElement objectForKey:[children objectAtIndex:i]];
[depthElement setObject:object forKey:key]; //I set the desired object but I dont know how to save it
for (int i = 0; i < children.count - 1; i++)
{
//Here i guess i would have to build the NSDictionary from inside out. Using a NSMutable array perhaps?
}
}
I hope you understand my problem. I hope Im not complicating things too much. Im just really tired and have been up for nearly 24 hours now and cant think of a way to solve this.
Thank you in advance.
I don't understand why you don't just use your:
[self writePlistContent];
to save it.
Surely it will save the entire contents of the plist.

objective c perform selector in background and autoreleasepool

I am developing an iphone application which has some data stored in a sqllite database. When my view loads i would like to load the data from the database on a background thread. The problem is the application keeps crashing and i dont know why.
The code:
-(id) init
{
if((self=[super init]))
{
[self performSelectorInBackground:#selector(loadList) withObject:nil];
}
}
-(void) loadList
{
#autoreleasepool
{
Loader * loader = [[Loader alloc] init];
NSMutableArray * array = [loader getItemList];
[array retain];
NSLog(#"Got %d items",[array count]);
[self performSelectorOnMainThread:#selector(createList:) withObject:array waitUntilDone:false];
[loader release];
}
}
-(void) createList: (NSMutableArray*) array
{
items = array;
int i;
Item * it;
for(i = 0; i < [items count]; i++)
{
it = [items objectAtIndex: i];
[it getName]; // crashes
// populate the list
}
}
Loader returns a NSMutableArray with Item objects. The application crashes when i call the item getName (which returns a NSString*). From what i understand it crashes because the item name properties is being released. What am i doing wrong?
Thanks!
It's likely to be a problem with whatever type of object you're using to populate array.
I'm unable to find finger-on-paper proof but I'm confident that performSelectorOnMainThread:withObject:waitUntilDone: retains its object. However if each of the items in array keeps a reference to loader then they need to take responsibility for retaining that object. It looks like you're attempting to keep it alive manually but — as Chuck alludes to — your call to performSelector... will return instantly and not wait for the call you've made to complete.
This particular bug appears to be that you're passing waitUntilDone:NO, so the array is being released immediately and consequently so are its items.
But in general, UIKit is not thread-safe, so this is just a touchy design. I would probably put the loading of this stuff in another class that handles the task for you instead of right in the view.
I'd put a breakpoint on the line:
it = [items objectAtIndex: i];
Then type
po it
in the debugger, and see what's in the name field. As a guess, I'd say one of two things: 1) the field that getName returns isn't initialized with an object (i.e. isn't a real NSString *) or that you're getting a C string from SQLite (which is what it usually returns) and you're trying to treat it as an NSString *. If it's the latter you can use [myCString stringWithUTF8String] to convert the C string into an NSString *

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);
}

Need To Make A NSMutableArray That Is Writable To Disk

I want to have a table view that is blank at start.
The user will be able to add entries and sub entries to design a workout routine.
For example, table is blank at first, then user adds "Day 1", and adds bench press, bicep curl, etc. into this Day 1 sub table. Then he adds a Day 2, and adds some other exercises in there.
I already have all these strings that user can choose from in a plist that is loaded into a different table that just sorts these strings my muscle group. What is the best way to go about this?
I hope my question is clear if not I will try better to reword it.
I've done something similar where I actually just wrote the contents of my array out to a PLIST file and steered clear of the keyed archiver. You can't use this approach if you have custom objects in your array. I believe your array (or dictionary) contents can only be of type nsstring, nsnumber, nsdata, nsarray, nsdate and nsdictionary (there could be more?!?).
I have a singleton that controls loading and writing the data from/to local storage. It also contains the single array object that I manipulate from my various view controllers.
Within init, I load my array property (deep mutable copy is a category method I've built for nssarray):
- (void) loadYourArray {
NSArray *tempArray = [NSArray arrayWithContentsOfFile:[Utils getFileStorageLocation]];
if (tempArray == nil) {
yourArray = [[NSMutableArray alloc] init];
} else {
yourArray = [[NSArray deepMutableCopy:tempArray] retain];
}}
when I'm ready to save my data, I simply call a persist method:
- (void)persistYourArray:(BOOL)broadcast {
[yourArray writeToFile:[Utils getFileStorageLocation] atomically:YES];
if (broadcast) {
// notify some peeps
}}

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.