Need To Make A NSMutableArray That Is Writable To Disk - iphone

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

Related

How to save nsdictionary of a subview to a mainview based off tableviewcell selection

I am currently parsing some xml that looks like this
<Rows>
<Row MANUFACTURERID="76" MANUFACTURERNAME="Fondont" ISMANU="F" ISAUTO="F"/>
<Row MANUFACTURERID="18" MANUFACTURERNAME="Anti" ISMANU="T" ISAUTO="T"/>
</Rows>
I parse it so that there is an array of dictionaries (each dictionary has the four values of the Row in it).
I then pass ManufacturerName to my startSortingTheArray method like this
if (dataSetToParse == #"ICMfg") // ICMfg is a string passed to this view from the parent view cell selection enabling me to pass different data sets to this view
{
//Filter results (ISAUTO = T)
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"%K like %#",#"ISAUTO",#"T"];
NSArray *filteredArray = [myDataArray filteredArrayUsingPredicate:predicate];
//Passes Manufacturer strigs over to startSortingtheArray method
[self startSortingTheArray:[filteredArray valueForKey:#"MANUFACTURER"]];
}
So from here all of the ManufacturerNames are sent to my method as an array of strings. I then use this array to set up all of my sections / index-scroller. The method below shows how I am doing this.
//method to sort array and split for use with uitableview Index
- (IBAction)startSortingTheArray:(NSArray *)arrayData
{
//If you need to sort incoming array alphabetically use this line of code
//TODO: Check values coming in for capital letters and spaces etc
sortedArray = [arrayData sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
//If you want the standard array use this code
//sortedArray = arrayData;
self.letterDictionary = [NSMutableDictionary dictionary];
sectionLetterArray = [[NSMutableArray alloc] init];
//Index scrolling Iterate over values for future use
for (NSString *value in sortedArray)
{
// Get the first letter and its associated array from the dictionary.
// If the dictionary does not exist create one and associate it with the letter.
NSString *firstLetter = [[value substringWithRange:NSMakeRange(0, 1)] uppercaseString]; //uppercaseString puts lowercase values with uppercase
NSMutableArray *arrayForLetter = [letterDictionary objectForKey:firstLetter];
if (arrayForLetter == nil)
{
arrayForLetter = [NSMutableArray array];
[letterDictionary setObject:arrayForLetter forKey:firstLetter];
[sectionLetterArray addObject:firstLetter]; // This will be used to set index scroller and section titles
}
// Add the value to the array for this letter
[arrayForLetter addObject:value];
}
//Reload data in table
[self.tableView reloadData];
}
from here I do several things to do with setting up the tableview after [self.tableView reloadData]; is called, The main thing being is that I set the cell up with the string values of the array.
//Display cells with data
NSArray *keys = [self.letterDictionary objectForKey:[self.sectionLetterArray objectAtIndex:indexPath.section]];
NSString *key = [keys objectAtIndex:indexPath.row];
cell.textLabel.text = key;
when the cell is then selected the string value inside the cell is then sent back to the main view and used later as a search parameter... The thing being is that I am setting up several parameters that will be used as one search string.
Looking back at the XML I parsed
<Rows>
<Row MANUFACTURERID="76" MANUFACTURERNAME="Fondont" ISMANU="F" ISAUTO="F"/>
<Row MANUFACTURERID="18" MANUFACTURERNAME="Anti" ISMANU="T" ISAUTO="T"/>
</Rows>
These are the values of columns inside an SQl table that has a keyvalue MANUFACTURERID that is also found in other tables that I parse. I would like to use these key values to restrict/refine other queries but I just cannot figure out how to pass them to my parentview where I set up all of the search parameters, that is my question how can I save the dictionary of values that is related to the users tableview selection from the subview. So that I can then pass one or some of those values back to the subview of a different dataset to restrict the information that is displayed dependent on the users previous selections.
This has taken me about an hour to type up. Hopefully it makes sense, I am still fairly new to iOS development and Objective C, and this concept is really pushing my capabilities and before I move on and end up hasing some crap together that I will have to fix later on I am hoping that one or some of you will be able to lend your experience in this type of this to me so I can get this right first time :)
If you need me to clarify anything or provide you more information that will help you help me just let me know.
Thanks in advance!
The common pattern for passing information backwards in your view controller hierarchy is to use delegation. You can achieve this in your scenario by implementing the following:
1) Define a protocol in the SearchParametersViewController, which represents your the parent view controller you mentioned.
#protocol SearchParametersViewControllerDelegate <NSObject>
#optional
- (void)searchOptionsSelected:(NSArray *)selectedSearchOptions;
#end
2) Conform to that protocol in your SearchOptionsSelectionViewController, which represents the table view controller that has a list of selections to choose from. Make sure to import or forward-declare the class the protocol is defined in (e.g. SearchParametersViewController) .
#import "SearchParametersViewController.h"
#interface SearchOptionsSelectionViewController <SearchParametersViewControllerDelegate>
3) Define a delegate property in your SearchOptionsSelectionViewController (assumes you are using ARC on iOS 5.0, 4.x use unsafe_unretained instead of weak. Use assign if the project is using manual memory management). This delegate object will contain a reference to your parent view controller (e.g. SearchParametersViewController). You do not want this property to be retained as to avoid retain cycles/circular references where one object references another, which in turn has a reference back to the first and neither object is ever deallocated.
#property (nonatomic, weak) id<SearchParametersViewControllerDelegate> delegate;
4) When instantiating the SearchOptionsSelectionViewController instance inside your parent view controller (SearchParametersViewController), set the delegate property to the parent view controller instance as represented by the self keyword. This ensures you can send the message (and corresponding data) backward in your view controller hierarchy, yet the object relationships remain loosely coupled. This delegate protocol could be conformed to in any other view controller, there are no tight relationships in the selection view controller back to the parent view controller, the only thing linking them is the flexible delegate protocol adoption by the selection view controller.
SearchOptionsSelectionViewController *selectionViewController = [[SearchOptionsSelectionViewController alloc] init];
selectionViewController.delegate = self;
5) Finally, in your SearchOptionsSelectionViewController table view's -tableView:didSelectRowAtIndexPath: delegate method, pass the data corresponding to the selected row back to your parent view controller (SearchParametersViewController) via the delegate method you defined in the SearchParametersViewControllerDelegate protocol. You must use the -respondsToSelector: method to ensure that the delegate object actually implements the -searchOptionsSelected: delegate method. To force this implementation, change #optional to #required above the method prototype in the protocol definition in step #1. self.someDataArray represents a the data source you are using with the selection table view controller. The specifics of the delegate protocol method and data object(s) sent back to the parent view controller can be changed, the important thing here is the delegation pattern and not having any tightly coupled relationships between the instances of either class, but especially backwards in the view controller hierarchy.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([self.delegate respondsToSelector:#selector(searchOptionsSelected:)])
{
NSArray *selectedObjs = [NSArray arrayWithObject:[self.someDataArray objectAtIndex:indexPath.row]];
[self.delegate searchOptionsSelected:selectedObjs]
}
}
6) Implement the delegate method inside SearchOptionsSelectionViewController.m
- (void)searchOptionsSelected:(NSArray *)selectedSearchOptions
{
// do what you need to with selectedSearchOptions array
}
Further reading:
Cocoa Fundamentals Guide - Delegates and Data Sources
Cocoa Core Competencies - Protocol
You could use the application delegate to achieve your goals here.
I'm going to assume your app has a structure a bit like this. Please excuse the crudity of this model.
Application delegate (A) --> Search Options View (B) --> Table where you do selections (C)
|
|
--> Some other view where you need the selection (D)
Your problem is that you need information to flow from C to D.
Your application delegate has the merit of being universally accessible via [[UIApplication sharedApplication] delegate]. So you can get a pointer to it from anywhere. From C, you can send your selection information back to A. A can either send this on automatically to D, or D can request it from A whenever it wants it.
A couple of points:
I won't expand any further on my answer at the moment because it's beer o' clock here now, plus I might have misunderstood your requirement. If you do need anything else, I will be up at baby o' clock in the morning UK time so there might be some delay.
Some people frown on using the application delegate as a "data dump" in the way I have suggested. Some of those people would rather set up a whole singleton class and treat that as a data dump instead. It seems to be one of those neverending arguments so I try not to get involved.
You have a few options, one is to use user defaults. It might be the easiest.
http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSUserDefaults_Class/Reference/Reference.html
Another is to post a notification with the information.
http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/nsnotificationcenter_Class/Reference/Reference.html

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

Core Data object into an NSDictionary with possible nil objects

I have a core data object that has a bunch of optional values. I'm pushing a table view controller and passing it a reference to the object so I can display its contents in a table view. Because I want the table view displayed a specific way, I am storing the values from the core data object into an array of dictionaries then using the array to populate the table view. This works great, and I got editing and saving working properly.
(i'm not using a fetched results controller because I don't have anything to sort on)
The issue with my current code is that if one of the items in the object is missing, then I end up trying to put nil into the dictionary, which won't work.
I'm looking for a clean way to handle this, I could do the following, but I can't help but feeling like there's a better way.
*passedEntry is the core data object handed to the view controller when it is pushed, lets say it contains firstName, lastName, and age, all optional.
if ([passedEntry firstName] != nil) {
[dictionary setObject:[passedEntry firstName] forKey:#"firstName"]
}
else {
[dictionary setObject:#"" forKey:#"firstName"]
}
And so on. This works, but it feels kludgy, especially if I end up adding more items to the core data object down the road.
What you could do is iterate through all of the object's properties using the objc_* runtime functions like so:
unsigned int property_count;
objc_property_t * prop_list = class_copyPropertyList([CoreDataObject class], &property_count);
for(int i = 0; i < property_count; i++) {
objc_property_t prop = prop_list[i];
NSString *property_name = [NSString stringWithCString:property_getName(prop) encoding:NSUTF8StringEncoding];
id obj = [passedEntry valueForKey:property_name];
[dictionary setObject:((obj != nil) ? obj : [NSNull null]) forKey:property_name];
}
free(prop_list);

trying to add dictionary objects to an NSMutableArray - afterward array returns null?

I just did a search for my particular question and although the answers are close, I can't get my head around the answer...so I need some assistance.
I'd like to populate an array (NSMutableArray I suppose) with a group of dictionary objects that are parsed from JSON strings...the dictionary part I got, the JSON parsing I got, but when I try to put these objects into the NSMutableArray and NSLog it I get (null)... here are my steps (in a general way)
edit 1:
-The array I am creating is called NewFinalArray. it is an NSMutableArray, declared at the .h file and synthesized (and now alloc'd and init'd) as noted in the viewDidLoad method of the DetailViewController. It's contents are to be displayed in a UITableView.
-In DetailViewController, I have been successful in creating a plain NSArray/NSMutableArray and populating it with values that display in my UITableView.
-In the new scenario, I am receiving the information to be displayed through JSON strings which are retrievable through dictionary objects. I am using the Stig JSON libraries for iPHone/iPad. I have no problems there.
-All I wanted to do is getting the existing dictionary objects (which I can loop through from the existing array and see) and add them to a new Array to be used for displaying menu items in my UITableview.
I declared my mutableArray in my .h file
#interface blah : ...,...,...{
NSMutableArray *newFinalArray;
// other vars and IBOutlets
}
#property (nonatomic, retain) NSMutableArray *newFinalArray;
// other #property and (IBAction) stuff
#end
I then synthesize it in my .m file... I even alloc/inited it at viewDidLoad (it's a DetailViewController)
#synthesize this,that, newFinalArray; // keep track of newFinalArray, that's the one I want
- (void)viewDidLoad {
// other code
[[newFinalArray alloc] init]; // ya returns a warning, about not responding to alloc, but whatever (for now)
// I also tested of course without having to do that.
in my method that uses newFinalArray, the method is a recursive function that calls itself. each time it calls, it should add the dictionary object to the array (or does it?)
-(void)digTree:(NSArray *)array{
for (NSDictionary *dictionary in array){
// looping through the array
[self newFinalArray addObject:[dictionary]];
// more other code, and somewhere along the way I recurse
[self digTree:anotherArray];
}
}
when I try to NSLog (#"my final array is %#", newFinalArray) I get (null).
I am probably missing something here. I tried to add "nil" at the end. I am a little new/green to this , so if someone can lend a hand and let me know how to populate my newFinalArray with these dictionary objects it would be most appreciated.
[[newFinalArray alloc] init];
should be:
newFinalArray = [[NSMutableArray alloc] init];
This line is wrong too:
[self newFinalArray addObject:[dictionary]];
it should be:
[newFinalArray addObject:dictionary];
The first thing I notice that is wrong, is it should be:
newFinalArray = [[NSMutableArray alloc] init];
in viewDidLoad. See if that fixes it. It looks like there are other things wrong as well, so turn on warnings and see what else the compiler warns you about for hints.
How are the dictionaries stored? An alternative/probably easier way to do this would probably be to use arrayWithObjects:. Also, when using addObject:, there is no need to add nil (in fact, you can't add nil).

Sorting an NSMutableArray containing matching "pairs" of values

I'd prefer not to change the way anArray is designed because it is also used elsewhere as a dataSource for a UITableView. The odd number values are "user names", and the even number values are their matching "dates". (The "pairs" must remain together.)
How would I sort "by user name"?
How would I sort "by date"?
Should I be using sortUsingFunction or sortUsingSelector?
-(void) test
{
NSMutableArray *anArray = [NSMutableArray arrayWithObjects:#"Zeke", #"01-Jan-2010", #"Bob", #"02-Jan-2010", #"Fred", #"03-Jan-2010", #"Susan", #"04-Jan-2010", #"Kim", #"05-Jan-2010", #"Debbie", #"06-Jan-2010", nil];
[anArray sortUsingFunction:SortByDate context:(void *)context];
}
NSComparisonResult SortByDate(NSMutableArray *a1, NSMutableArray *a2, void *context)
{
// What goes here to keep the pair-values "together"?
}
Why aren'e you using a NSDictionary? You can have user names as keys and the dates as corresponding values. Then if you want to sort on user names, just use [dictObj allkeys] to get an array containing just the keys. Sort them as you prefer and then when displaying display the sorted array of keys and fetch the corresponding value for the key and display along side. Similarly, you can get all the values using [dictObj allValues] sort them and display them along with keys.
I'd prefer not to change the way anArray is designed because it is also used elsewhere as a dataSource for a UITableView
Does that mean cells alternate between displaying usernames and dates, or are you multiplying/dividing by 2?
Either way, you need to fix your model. The bit where you go "oops, sorting doesn't work" should be a big hint that you haven't chosen a good representation of your data. Adding a workaround will only lead to more tears later.
I'd suggest something like this:
Create a class to represent your (username,date) tuple.
Add -compareByDate: and -compareByUsername: methods.
[array sortUsingSelector:#selector(compareByDate:)]
If you want to display usernames and dates in alternating cells, then it's still easy to do, but I'd simply use a taller cell and save myself some pain.
Note: The overhead of Objective-C method calls means that you can get a significant performance increase by using sortUsingComparator: and defining appropriate functions (you can stick the function between #implmentation and #end to let them access protected/private ivars) but it's not really worth doing this unless you've determined that sorting is a bottleneck.
Something like this could work:
#class SortWrapper: NSObject
#property (copy,readwrite,atomic) NSString* name;
#property (copy,readwrite,atomic) NSDate* date;
#end
...
- (void)test
{
NSArray* names = #[#"Alice", #"Bob", #"Cameron"];
NSArray* dates = #[aliceDOB, bobDOB, cameronDOB]; // NSDate objects
// turn into wrapped objects for sorting
NSMutableArray* wrappedObjects = [NSMutableArray array];
for ( int i=0; i<names.count; i++ ) {
SortWrapper* wrapped = [[SortWrapper alloc] init];
wrapped.name = names[i];
wrapped.date = dates[i];
[wrappedObjects addObject:wrapped];
}
// to sort by date:
NSArray* sortedByDate = [wrappedObjects sortedArrayUsingComparator:^NSComparisonResult(SortWrapper* obj1, SortWrapper* obj2) {
return [obj1.date compare:obj2.date];
}];
// to sort by name:
NSArray* sortedByName = [wrappedObjects sortedArrayUsingComparator:^NSComparisonResult(SortWrapper* obj1, SortWrapper* obj2) {
return [obj1.name compare:obj2.name];
}];
}