iPhone SDK NSDictionary and NSArray confusion - iphone

I have been trying to get this to work for about three solid days and get my mind around it. Can someone advise.
I have built the basics and they all work great but when I try to do that extra bit I cannot get my head around it.
I am trying to build a table of technical terms, from a plist. This is an indexed and sections table by the alphabet.
This works fine but when I then try to add the next level for each term's definition in a new viewcontroller I can't seem to get the code or the plist structure right.
At the moment I have created two plists. One with a dictionary of the alphabet in 26 arrays, within each array is a series of technical terms. All this works great.
Then I've created another plist of definitions as an array of dictionaries, one for each word/definition pair. I'm expecting to be passing the #"word" key from the view controller to the detailviewcontroller then picking up the #"definition". I don't know whether this is right or wrong(?)
My code shows the technical term table great but when a row is selected it crashes. I know it's to do with the code for passing the detailviewcontroller's reference so the detailview can pick up the definition - but I've no idea how to solve it. I've posted parts of my code here for someone to look at help. Any ideas?
NSString *wordPath = [[NSBundle mainBundle] pathForResource:#"newsortedglossary" ofType:#"plist"];
NSDictionary *wordDict = [[NSDictionary alloc] initWithContentsOfFile:wordPath];
self.words = wordDict;
[words release];
NSArray *wordArray = [[words allKeys] sortedArrayUsingSelector:#selector(compare:)];
self.wordKeys = wordArray;
NSString *definitionPath = [[NSBundle mainBundle] pathForResource:#"newnewdefinitionglossary" ofType:#"plist"];
NSDictionary *definitionDict = [[NSDictionary alloc] initWithContentsOfFile:definitionPath];
self.definitions = definitionDict;
[definitions release];
didSelectRow here.........
GlossaryDetailViewController *glossaryDetailViewController = [[GlossaryDetailViewController alloc]
initWithNibName:#"GlossaryDetailView" bundle:nil];
NSLog(#"did select-2"); // CRASHES HERE with NSDictionary may not respond to objectAtIndex
glossaryDetailViewController.definition = [self.words objectAtIndex:indexPath.row];
NSLog(#"did select-3");
[self.navigationController pushViewController:glossaryDetailViewController animated:YES];
NSLog(#"did select-4");
[glossaryDetailViewController release];
detailViewController here.......
- (void)viewDidAppear:(BOOL)animated {
NSLog(#"didappear");
self.glossaryWordDefinition.text = [definition objectForKey:#"definition"];

It seems that you are trying to access the members of dictionary by using an index, instead of using a key to lookup the associated value.
In your didSelectRow you probably want this:
glossaryDetailViewController.definition = [self.wordKeys objectAtIndex:indexPath.row];
The difference is that now you are trying access the members of an array with the index.

Related

iPhone Programming - complex JSON Parsing - UITableView

Hey guys :) I am quite new to stack overflow and iPhone programming. I am trying to parse a complex JSON to display some stuff in the UITableView.
a part of the JSON structure -
{"1":{"1":"Ent1","done":"No"},"2":{"1":"Ent2","done":"No"}}
I am able to parse through the main keys "1" and "2" and able to grab the values corresponding to the key "1" inside {"1":"Ent1","done":"No"}, {"1":"Ent2","done":"No"} store them into a dictionary/ a string with the following code :
for (NSString *key in dict)
{
NSString *answer = [dict objectForKey:#"1"];
NSLog(#"%#", answer);
}
The result is Ent1 and Ent2 because the code iterates over the for loop and checks for the objects with key "1".
The problem is this - I want to store both the values(Ent1 and Ent2) into an array.
I use the following code:
NSMutableArray *array = [[NSMutableArray alloc] initWithObjects: answer, nil];
but it just takes the last index in the dictionary which is Ent2.
Could you please tell me how could I add both the values for key 1 into an array?
Thanks in advance :)
To add to jamapag's answer, you can also use JSON libraries in objective C which do all the work for you like SBJSON or YAJL, or even as part of the more recent versions of the mac/iOS SDKs, NSJSONSerialization.
NSMutableArray *array = [[NSMutableArray alloc] init];
for (NSString *key in dict)
{
NSString *answer = [dict objectForKey:key];
[array addObject:answer];
}

incompatible pointer types assigning to nsarray from nsdictionary

I'm new to iPhone development and have had great success with with answers from here so I am hoping to receive help directly. I am reading data into a tableview from a plist. The application works fine but I get 2 warnings when I compile. I know why I get the errors but I have been unsuccessful with resolving the issues. Although this app works I really would like to resolve the warnings efficiently. When I tried changing the NSDictionary to NSArray the warning goes away but the table is no longer populated.
Any help would be greatly appreciated.
Staff and Data are defined as NSArray in the Delegate .h file. The warnings show in the delegate .m file below.
My Delegate has the following:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
// Add the tab bar controller's current view as a subview of the window
NSString *Path = [[NSBundle mainBundle] bundlePath];
NSString *DataPath = [Path stringByAppendingPathComponent:#"Data.plist"];
NSString *SPath = [[NSBundle mainBundle] bundlePath];
NSString *StaffPath = [SPath stringByAppendingPathComponent:#"Staff.plist"];
NSDictionary *tempDict = [[NSDictionary alloc] initWithContentsOfFile:DataPath];
**self.data = tempDict;**
[tempDict release];
NSDictionary *staffDict = [[NSDictionary alloc]initWithContentsOfFile:StaffPath];
**self.staff = staffDict;**
[staffDict release];
In my staff ViewController I have the following:
if(CurrentLevel == 0) {
//Initialize our table data source
NSArray *staffDict = [[NSArray alloc] init];
self.tableDataSource = staffDict;
[staffDict release];
Midwest_DigestiveAppDelegate *AppDelegate = (Midwest_DigestiveAppDelegate *)[[UIApplication sharedApplication] delegate];
self.tableDataSource = [AppDelegate.staff valueForKey:#"Rows"];
}
else
self.navigationItem.title = CurrentTitle;
An NSArray holds a one dimensional list of items where an NSDictionary maps keys to values.
Array:
[a, b, c]
Dictionary:
{#"a" = #"first item", #"b" = #"second item"}
Could you declare data as NSDictionary *data; and populate it as data = [[NSDictionary alloc] initWithContentsOfFile:DataPath];
You then access values in the dictionary with [data valueForKey:#"key"]
Everything in your code suggests that the staff and data properties are NSDictionary instances. You initialize them to dictionary objects and you reference them as dictionary objects. Why then are you declaring them as NSArray objects?
You should change how they are declared so they are NSDictionary in your header file rather than NSArray. That seems to me the most logical way to remove your warnings.
This should still work assuming the contents of your "staff" NSDictionary has a key named "Rows" whose value is an NSArray. The code you have to initialize self.tableDataSource with an empty NSArray seems redundant, as you immediately overwrite the value with the
self.tableDataSource = [AppDelegate.staff valueForKey:#"Rows"];
line in your code

Reading and Writing to an NSDictionary?

In my app I have a table view where the user presses an "add" button in the nav bar and a new cell is added to the table view. The data from the table is loaded from an NSArray and each index in the array is storing an NSMutableDictionary with 4 Key-Value pairs. This array is saved to a .plist every time a cell is added. Now when the user selects a row in the table a detail view gets loaded. This detail view simply loads the data from the saved .plist depending on what row was selected.
In this detail view I want to allow the user to edit the data in the dictionary for that specific row. I've been trying different things and I can read the data from the dictionary and load it into the view but when I try and save the data back to the dictionary my app keeps terminating.
Can someone explain to me the proper way to reading and writing data to a an NSMutableDictionary?
This is how i'm writing the data back to the .plist:
NSMutableArray *array = [[NSMutableArray alloc] initWithContentsOfFile:filePath];
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
dict = [array objectAtIndex:selectedRow];
NSString *tempA = [[NSString alloc] initWithFormat:#"%d pts", ivar_A];
NSString *tempB = [[NSString alloc] initWithFormat:#"%d pts", ivar_B];
NSString *tempC = [[NSString alloc] initWithFormat:#"%d pts", ivar_C];
[dict setValue:tempA forKey:#"keyA"];
[dict setValue:tempB forKey:#"keyB"];
[dict setValue:tempC forKey:#"keyC"];
[tempA release];
[tempB release];
[tempC release];
[array insertObject:dict atIndex:selectedRow];
//Save the modified array back to the plist
[array writeToFile:[self dataFilePath] atomically:YES];
[dict release];
[array release];
if you use [[NSMutableDictionary alloc] dictionaryFromContentsOfFile:#"pathtofile.plist], the contents of the dictionary will still be immutable, even if the top level container is mutable. It is specifically stated in Apple's reference.
NSDictionary Reference
The dictionary representation in the
file identified by path must contain
only property list objects (NSString,
NSData, NSDate, NSNumber, NSArray, or
NSDictionary objects). For more
details, see Property List Programming
Guide. The objects contained by this
dictionary are immutable, even if the
dictionary is mutable.
If you need to make the data structure writable again, you will need to make a copy of the loaded data structure using mutable types, like
[NSMutableDictionary dictionaryWithDictionary:oOrig]
etc.

Refresh Value in NSMutableDictionary using ViewWillAppear

See this code:
int i = years;
NSLog(#"Years i: %i",i);
NSString *syears= [NSString stringWithFormat:#"%d",i];
menuList = [[NSMutableArray alloc] init];
[menuList addObject:[NSMutableDictionary
dictionaryWithObjectsAndKeys:
#"years", kLeftKey,
syears, kRightKey, nil, kControllerKey, nil]];
The above is an extract from ViewWillAppear. When I switch between tabs ViewWillAppear is doing it's job and the data is updating down as far as NSString *syears= [NSString stringWithFormat:#"%d",i]; but after that my array doesn't get updated with the latest value of syears. I tried using setObject and setValue but these don't work with NSMutableArray. Any ideas?
Keys and values in NSDictionary need to be non-nil NSObjects. So you've definitely got an issue with a nil in there, and there may be a question about kLeftKey et al, for which you haven't shown the definitions.
More generally, it would help to know what you are doing with menuList and what you mean by "my array doesn't get updated". Are you trying to display it? Using it as a data source somewhere? You are creating the array anew here -- could a previous reference be being left around and displayed?

Creating Arrays from a plist file in iphone SDK 3.3 and up : objective c

I have run into this issue and have put some major time into finding the answer. I am somewhat new to objective c but not to programming.
Here is my question.
I have a plist file with this structure
root {
A (
{songTitle : contents of song},
{songTitle : contents of song}
),
B (
{songTitle : contents of song}
),
C (
{songTitle : contents of song}
),
... kepps going
}
Sorry if the the plist structure is not correct.
Pretty much I have a root dictionary (that is what it comes with) that contains an array of A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,...Z (alphabet)
Each letter of the alphabet array contains 1 or more dictionaries that have a key, value pair of songTitle (this could be any string) as the key and the song lyrics for the value.
My issue here is I need to create an array of all song titles and have been having a rough time trying to find out how to do this. I own 4 books on object c and none of them go into detail about multidimensional arrays and how to access pieces inside them.
I have created an array with all the letters and have created an array that contains the objects from each letter.
Like I stated before I need to find out how to make an array that contains each song title.
If you can help me that would save me a lot of time.
Thanks,
Wes
I am guessing you are suggesting I change my root from a dictionary to an array?
Maybe it might be better to show my code here.
Also I have attached an updated version of my plist file
Sorry seems I cannot add the image here but you can view it
http://www.wesduff.com/images/forum_images/plist_examp.png
So as you can see I have updated the plist file to show the array of letters that each contain multiple dictionaries. Each dictionary has a songTitle and a songLyrics.
How can I write code to get an array of songTitles.
Here is what I have come up with so far
NSString *path = [[NSBundle mainBundle] pathForResource:#"songs" ofType:#"plist"];
NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:path];
//This gives me an array of all the letters in alphabetical order
NSArray *array = [[dict allKeys] sortedArrayUsingSelector:#selector(compare:)];
/**
Now I need to find out how to get an array of all songTitles
**/
I am still working on this and looking through what others have written but have not found anything yet.
As the first answer has suggested, should I change the root to an array or keep it as I have it in this plist image I have attached.
Thanks again,
Wes
Ok so I did some more digging and came up with this from the plist file that was included in this picture
http://www.wesduff.com/images/forum_images/plist_examp.png
- (void)viewDidLoad {
//path for plist file
NSString *path = [[NSBundle mainBundle] pathForResource:#"songList" ofType:#"plist"];
//dictionary created from plist file
NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:path];
//release the path because it is no longer needed
[path release];
//temp array to hold an array of all alphabetical letters
NSArray *array = [[dict allKeys] sortedArrayUsingSelector:#selector(compare:)];
//assign array to allLetters array
self.allLetters = array;
//Create two mutable arrays for the songArray (could do a little cleaner job of this here)
NSMutableArray *songArray = [[NSMutableArray alloc] init];
NSMutableArray *songTitles = [[NSMutableArray alloc] init];
//Add messy array to songArray then we can work with the songArray (maybe something better to do here)
for(id key in dict)
{
[songArray addObject:[dict objectForKey:key]];
}
//temp array to hold a messy array for all of the songTitles
NSArray *tempArray = [songArray valueForKey:#"songTitle"];
//go through the temparray and clean it up to make one array of all song titles and sort them
for (NSUInteger i = 0; i < [tempArray count]; i++) {
[songTitles addObjectsFromArray:[[tempArray objectAtIndex:i] sortedArrayUsingSelector:#selector(compare:)]];
}
//assign all song titles to our array of songTitles
self.allSongTitles = songTitles;
[dict release];
[allSongTitles release];
[songArray release];
[tempArray release];
[array release];
[super viewDidLoad];
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
}
I am sure there is probably a better way to do this but this is what I have come up with on my own. Thanks
If you have single array with the contents of all the letters, the rest is fairly simple. Iterate through the objects and call the dictionary method allKeys on each one. Each call to allKeys will return an NSArray containing the keys of that specific dictionary, which you can then place into another array.
EDIT
I made a mistake, didn't go deep enough. This is what I would do:
NSString *path = [[NSBundle mainBundle] pathForResource:#"songs" ofType:#"plist"];
NSDictionary plistDict = [NSDictionary dictionaryWithContentsOfFile:path]; //not using alloc and init means this isn't retained, so it will be autoreleased at the end of the method
NSArray *allLetterContents = [plistDict allValues]; // array of arrays, where each element is the content of a 'letter' in your plist (i.e. each element is an array of dictionaries)
NSMutableArray *allSongTitles = [[[NSMutableArray alloc] init] autorelease];
for(NSArray *oneLetterContents in allLetterContents)
{
for(NSDictionary *song in oneLetterContents)
{
[allSongTitles addObject:[song objectForKey:#"songTitle"]]
}
}
return allSongTitles;
This array isn't guaranteed to be sorted alphabetically, so you'll have to call [sortedArrayUsingSelector:] on it.
Reference:
NSMutableArray Class Reference
NSDictionary Class Reference