NSMutableArray Overwriting Previously Stored Elements - iphone

I currently have a function written called saveWorkout that saves an NSMutableArray to another NSMutableArray from a Singleton class. This function works the first run through, however, when I run it a second time, it erases what was previously stored in element 0 and replaces it with the new array (which is a collection of strings gathered when a user clicks on a table).
Here is my function:
-(IBAction)saveWorkout{
WorkoutManager *workoutManager = [WorkoutManager sharedInstance];
[[workoutManager workouts] insertObject: customWorkout atIndex: 0];
NSLog(#"%#", [workoutManager workouts]);
}
customWorkout is what initialially creates the NSMutableArray (based on what the user clicks). Thus, if my first array is comprised of blah1, blah2, those two values will be stored in the workouts array. However, if I then click blah2, blah 3, the workouts array will have two identicle arrays (blah2, blah3) and it doesn't retain the first array. Any idea why this is happening?
Here is how I form customWorkout:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
NSString *str = cell.textLabel.text;
[customWorkout insertObject:str atIndex:0];
//Test Code: Prints Array
NSLog(#"%#", customWorkout);
}

I will tell you the logical mistake that you are making....
you are using the same customWorkout object over and over again to insert in the workouts array... (so its the same pointer) whereas what you need to do is to create a copy of the customWorkout array and then insert it in the workout array ... try this instead....
[[workoutManager workouts] insertObject: [[customWorkout mutableCopy] autorelease]atIndex: 0];
this should work unless you are doing something else in your code.

[[workoutManager workouts] insertObject: customWorkout atIndex: 0]; does not copy the contents of customWorkout ... instead it just retains a reference to customWorkout. So your code is simply storing multiple references to the same object, which you end up (unintentionally) editing on the second run through.
You need to either:
Copy the customWorkout object via copy when you store it in workouts
OR:
Assign customWorkout to a new NSMutableArray instance each time after you do a saveWorkout
Either route should keep you from modifying the NSMutableArray you store into the workouts collection. The first option is probably more clear in terms of memory-management...

Related

How do I add objects to an array that is inside an array?

I am trying to add an object to an array that is inside an array.
Here is my storyboard. Screen A is a simple tableView containing an array with object A, Screen B adds new objects to screen A. Each object A contains an array with detail (object B), these details are shown in screen C and you add details to object A in screen D.
So my model is as you can see above. I got Array A containing object A, each object contains Array B containing object B. Both my arrays are Mutable.
Object A = budget
Object B = item
I can not figure out how to add object B to array B.
- (void)addItemViewController:(AddItemViewController *)controller didFinishAddingItem:(Item *)item
int newRowIndex = [self.budgets.items count];
[self.dataModel.budgetsList addObjectFromArray:item];
NSLog(#"Item with name %# added", item.name);
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:newRowIndex inSection:0];
NSArray *indexPaths = [NSArray arrayWithObject:indexPath];
[self.tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationAutomatic];
[self dismissViewControllerAnimated:YES completion:nil];
This is what I am doing so fare. My problem here is that I am adding item (object B) into budget array (array A). :/
Thanks in advance.
When you are doing this:
NSArray *indexPaths = [NSArray arrayWithObject:indexPath];
You are mixing Presentation with data. What you need here is get the object element (the array in your case) that this indexpath corresponds to. As per the Table view design pattern, every table view reads its cells from an underlying collection of data objects. Have you defined this object (most preferably in separate objective-c .m and .h files)?
As for adding array into another array, NSArray just expects NSObject as element, so it is pretty straight-forward to add one into another.
NSArray *arrayB = [[NSArray alloc] init]; //any other initialization is good as well
NSArray *arrayA= [NSArray arrayWithObject:arrayB];
The above code is valid for any pair of NSArrays in your code.
If you want to add an object to Array B, then Use:
[[[array A objectAtIndex:indexPath] arrayB] addObject:yourObject];
Or you can use (this is an expansion of above code):
ObjectA *temp = [array A objectAtIndex:indexPath];
NSMutableArray *tempArray = [temp arrayB];
[tempArray addObject:yourObject];
Cast your object B to Item then do
[self.dataModel.budgetList replaceObjectAtIndex:11 withObject:(Item)item];
This code assume that you want to replace the existing object inside A and the the index is 11. If you want to add you just use insertObjectAtIndex: withObject:

How to updates array values in iphone app

I have to updates array values at position 2 and 4. How will I update array values. I am initializing my array like below.
ArrayrData=[[NSArray alloc]initWithArray:statuses]; // where statuses is also an array
You can't change the value of NSArray. So initialize your array as Mutable Array
NSMutableArray *ArrayrData=[[NSMutableArray alloc]init];
[ArrayrData addObjectsFromArray:statuses];
You can update the value in a NSMutableArray using,
– replaceObjectAtIndex:withObject:
– replaceObjectsAtIndexes:withObjects:
– replaceObjectsInRange:withObjectsFromArray:range:
– replaceObjectsInRange:withObjectsFromArray:
Use the method
- (void)replaceObjectAtIndex:(NSUInteger)index withObject:(id)anObject;
if u want to replace them with new objects or simply access the objects at index 2 and 4 and then modify them using
[ArrayrData objectAtIndex:idx]; //idx is the index of the object you want to access, in ur case it is 2 or 4.
You can change the NSMutableArray dynamically in the application as follows:
NSMutableArray *ArrayData = [[NSMutableArray alloc]init];
[ArrayData addObjectsFromArray:statuses];
And now whenever you want to replace any object in array then you can simply replace using the "replaceObjectAtIndex" method as follows:
[ArrayData replaceObjectAtIndex:2 withObject:#"your Object or data"];
[ArrayData replaceObjectAtIndex:4 withObject:#"your Object or data"];
Let me know if you have any questions.

Can I have 2 arrays within a plist?

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

Is this the correct way to reloadData on a UITableView?

I am trying to append objects to my data source and then reload the table. Is this the correct way of approaching it?
self.items is my datasource
//Copy current items
self.itemsCopy = [self.items mutableCopy];//[[NSMutableArray alloc] initWithArray:self.items copyItems:NO];
NSLog(#"Copy Size before append: %d",[itemsCopy count]);
//Get new items
int lastMsgID = [self getLastMessageID];
[self.coreData getMoreMessages:self.title lastID:lastMsgID]; //This will update self.items with 30 objects
//Append new items
[itemsCopy addObjectsFromArray:self.items];
//Empty items and copy itemsCopy
[self.items removeAllObjects];
self.items = [self.itemsCopy mutableCopy];
NSLog(#"Actual Size after append: %d",[self.items count]);
//Reload data
[tableView reloadData];
try
[tableView reloadData];
in the viewWillAppear method....
Yes, looks about right to me.
Depending on how your properties are declared, you may have some memory issues here.
If your properties specify retain as an attribute, then setting assigning a copy of an array to them will result in a retain count of 2 on the object rather than the intended 1.
Eg.
self.myProperty = [myArray mutableCopy];
mutableCopy returns a newly allocated object with a retain count of one. The caller owns this object and is responsible for releasing it. Then you assign it to your property which retains it, increasing the retain count to 2. The object will be leaked when the owner is deallocated because the property will only be released once.
The solution would be to release the mutable copy after assigning and retaining it to to the property like this.
self.myProperty = [myArray mutableCopy];
[self.myProperty release];
Alternatively you could auto release the result of mutableCopy. However there seems to be some controversy surrounding the usage of autorelease on the iPhone. I personally don't see a problem with it unless it's used in a tight loop that iterates many many times in a short period of time.
self.myProperty =[[myArray mutableCopy] autorelease];
If you're using CoreData, then ideally you should be using a NSFetchedResultsController to manage the data for the tableview. (You did ask for the "correct" way :-)
You'll end up with a more memory efficient app.
See the iPhoneCoreDataRecipes Sample for a good example

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.