I have to UITableview, section headers titles contained in a dictionnary, contents in an array associated with each title (those arrays create the cells).
It is ok that many have answered here about ordering dictionnary, that is was pretty difficult, etc…
Even if a dictionnary can('t, or with difficulties) be ordered, how does it keeps the same order everytime ?
example
Let's say i end up with a table view with two sections (titled), each containing some cells
A dictionnary is declared, it contains the section titles.
NSMutableDictionary *menuEntries;
For each of those dictionnary entries, a different array associated (which then is used to create and populate the cells). We will have two sections (so two keys in the dictionnary), for some reasons we use two different array that we are going to associate to those keys
NSArray *mainMenuArray;
NSMutableArray *magazineMenuArray;
The first one is populated like this (
mainMenuArray = [[NSArray alloc] initWithObjects: btn1,btn2,btn3,btn4,nil];
The second array (magazineMenuArray) is populated via some json call (not showing here how, but everythings works fine)
So we end up up setting the dictionnary
menuEntries = [[NSMutableDictionary alloc] init];
[menuEntries setObject:mainMenuArray forKey:#"First section"];
[menuEntries setObject:self.magazineMenuArray forKey:#"Second section"];
In the end, it works pretty well, we have defined some attributes for the arrays' object, one for the title, one for the action to be called, pretty cool.
!! BUT !!
Second section appears before first section. Nothing to do about/against it. Always.
I can hear that a NSDictionnary CAN'T BE ORDERED, ok, but I reeeeeeally feel like, in that case, IT IS ORDERED somehow.
That is very confusing.
NSDictionary keeps the same order every time, but that is an arbitrary order based on the hash codes of the objects that you insert as keys. If the dictionary is mutable, inserting or removing objects can change the ordering of the keys that are already in the dictionary.
Although it is not possible to order the dictionary itself, it is certainly possible to order its keys into a separate array, and then walk that array in order, pulling the objects by key from the(unordered) dictionary.
Edit:
You say that you have
NSMutableDictionary *menuEntries;
Which is populated as:
menuEntries = [[NSMutableDictionary alloc] init];
[menuEntries setObject:mainMenuArray forKey:#"First section"];
[menuEntries setObject:self.magazineMenuArray forKey:#"Second section"];
If you want it to respect the order in which you populate it, you should use a NSMutableArray instead, e.g.:
NSMutableArray *menuEntries;
And then, you can populate that array with dictionary entries with, at the very least, two keys, something for the title of the section and something with the rows for that section. Thus:
menuEntries = [[NSMutableArray alloc] init];
[menuEntries addObject:[NSDictionary dictionaryWithObjectsAndKeys:
#"First section", #"title",
mainMenuArray, #"rows",
nil]];
[menuEntries addObject:[NSDictionary dictionaryWithObjectsAndKeys:
#"Second section", #"title",
self.magazineMenuArray, #"rows",
nil]];
Thus,
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [menuEntries count];
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
NSDictionary *section = [menuEntries objectAtIndex:section];
return [section objectForKey:#"title"];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSDictionary *section = [menuEntries objectAtIndex:section];
return [[section objectForKey:#"rows"] count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSDictionary *section = [menuEntries objectAtIndex:indexPath.section];
NSArray *rows = [section objectForKey:#"rows"];
id row = [rows objectAtIndex:indexPath.row];
// I didn't know how mainMenuArray and self.magazineMenuArray were populated,
// so I used a data type of `id` for the row, but you can obviously replace
// that with whatever is appropriate, e.g., NSDictionary* or whatever.
// proceed with the configuring of the cell here
}
Personally, I wouldn't use the literal strings #"title" and #"rows" all over the place, but rather define constants like the following, include these at the start of the implementation, and use them instead of the literal strings. But I'm sure you get the basic idea.
NSString * const kTableTitleKey = #"title";
NSString * const kTableRowsKey = #"rows";
Regardless, this outlines a very common data model I use behind my UITableView objects. It's a nice logical structure that corresponds to the table view itself. Essentially it is an array of sections, each of which is a dictionary with two keys, one for the title and one for the rows of the section. The value for that "rows of the section" is, itself, an array, one entry for every row of the table. It sounds complicated, but as you see above, it actually makes the implementation very, very simple.
My original answer was provided before OP supplied any information about the nature of the data structures. Thus I provided an answer to the more abstract question of how does one sort an array of dictionary entries. I retain that answer for historical reference, though:
Original answer:
I'm not sure how you are storing your dictionary and how you represent rows in your table, but a common pattern is to have an array of dictionary items:
NSArray *array = #[
#{#"id" : #"1", #"name":#"Mo", #"age":#25},
#{#"id" : #"2", #"name":#"Larry", #"age":#29},
#{#"id" : #"3", #"name":#"Curly", #"age":#27},
#{#"id" : #"4", #"name":#"Shemp", #"age":#28}
];
You can then sort that via name, like so:
NSSortDescriptor *descriptor = [[NSSortDescriptor alloc] initWithKey:#"name"
ascending:YES];
NSArray *sortedArray = [array sortedArrayUsingDescriptors:#[descriptor]];
NSLog(#"array = %#", array);
NSLog(#"sortedArray = %#", sortedArray);
There are a whole series of sorting methods, so check out Sorting in the NSArray Class Reference.
Related
I am trying to display sections and rows correctly for my uiTableView.
I have had great help from one contributor and am fairly close to fixing my issue. The Issue can be seen here. Its not far off being right, its just the sections that need to be sorted.
It is repeating the section titles instead of only showing it once. Im not sure exactly how to fix this.
// Find out the path of recipes.plist
NSString *path = [[NSBundle mainBundle] pathForResource:#"lawpolice" ofType:#"plist"];
// Load the file content and read the data into arrays
self.dataArray = [NSArray arrayWithContentsOfFile:path];
//Sort the array by section
self.sortedArray = [self.dataArray sortedArrayUsingDescriptors:#[
[NSSortDescriptor sortDescriptorWithKey:#"Section" ascending:YES],
[NSSortDescriptor sortDescriptorWithKey:#"Title" ascending:YES]]];
self.temp = [[NSMutableDictionary alloc] init];
for (NSDictionary *dict in self.sortedArray) {
NSMutableArray *array = self.temp[dict[#"Section"]];
// No items with the same section key stored yet, so we need to initialize a new array.
if (array == NULL) {
array = [[NSMutableArray alloc] init];
}
// Store the title in the array.
[array addObject:dict[#"Title"]];
// Save the array as the value for the section key.
[self.temp setObject:array forKey:dict[#"Section"]];
}
self.policePowers = [self.temp copy]; // copy returns an immutable copy of temp.
//Section for sorting
self.sectionArray = [self.sortedArray valueForKeyPath:#"Section"];
NSLog(#"%#", self.sectionArray);
//Title
self.namesArray = [self.sortedArray valueForKeyPath:#"Title"];
//Offence
self.offenseArray = [self.sortedArray valueForKeyPath:#"Offence"];
//Points to Prove
self.ptpArray = [self.sortedArray valueForKeyPath:#"PTP"];
//Action
self.actionsArray = [self.sortedArray valueForKeyPath:#"Actions"];
//Notes
self.notesArray = [self.sortedArray valueForKeyPath:#"Notes"];
//Legislation
self.legislationArray = [self.sortedArray valueForKeyPath:#"Legislation"];
//PNLD
self.pnldArray = [self.sortedArray valueForKeyPath:#"PNLD"];
//Image
self.imageString = [self.sortedArray valueForKeyPath:#"image"];
titleForHeaderInSection
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
return [self.sectionArray objectAtIndex:section];
}
numberOfSectionsInTableView
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [self.policePowers count];
}
numberOfRowsInSection
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSArray *sectionrows = self.policePowers[self.sectionArray[section]];
return [sectionrows count];
}
Update
To be clear, if two items have the same Section value, I want to automatically group them into an array and have that array mapped to the Section value at the end
NSDictionary dictionaryWithObjects:forKeys: basically loops through two arrays and maps the object in one array at the current index as the key for the object in the other array at the same index. When you're calling
self.policePowers = [NSDictionary dictionaryWithObjects:self.namesArray forKeys:self.sectionArray];
it therefore maps the items in self.sectionArray as the keys for the items in self.namesArray. Looking at your plist file, the "Title" keypath (which is mapped to self.namesArray) has a value of string, so your NSLog results make sense, as self.namesArray is an array of strings, not an array of arrays.
I'm not sure how you were supposed to get a result like
"Alcohol: Licensing/Drive unfit" = {
"Drive/attempt to drive/in charge whilst unfit or over",
"Drive/attempt to drive/in charge whilst unfit or over",
"Drive/attempt to drive/in charge whilst unfit or over",
}
Where is that array supposed to come from?
-- EDIT --
I don't think there's a concise way to accomplish what you want, so it'd have to be done manually. I haven't actually used [NSArray arrayWithContentsOfFile:path] before, so is self.dataArray an array of dictionaries with each item representing one of the items in the plist (Item 44, Item 45, etc)? If so, you could do something like this:
NSMutableDictionary *temp = [[NSMutableDictionary alloc] init];
for (NSDictionary *dict in self.dataArray) {
NSMutableArray *array = temp[dict[#"Section"]];
// No items with the same section key stored yet, so we need to initialize a new array.
if (array == null) {
array = [[NSMutableArray alloc] init];
}
// Store the title in the array.
[array addObject:dict[#"Title"]];
// Save the array as the value for the section key.
[temp setObject:array forKey:dict[#"Section"]];
}
self.policePowers = [temp copy]; // copy returns an immutable copy of temp.
-- EDIT AGAIN --
The app crashes because self.policePowers is an NSDictionary, not an NSArray; thus it doesn't have an objectAtIndex: method. If you're trying to get the section title, try this instead:
return [self.sectionArray objectAtIndex:section];
Furthermore, if you're working with a table view, I'd basically have self.sectionArray sorted whichever way you like, then whenever I needed to populate data in each section, I would use self.policePowers[self.sectionArray[section]] to return the array of titles mapped to that section title.
-- YET ANOTHER --
If you break it up into the following lines, where is the NSRangeException thrown? If you NSLog, do the results match what you expect?
NSString *title = self.sortedKeys[indexPath.section];
NSArray *array = self.policePowers[title];
NSString *value = array[indexPath.row];
- (NSArray *)combinedStrings {
return [[self.numberOfUsers arrayByAddingObjectsFromArray:self.numberOfModerators] arrayByAddingObjectsFromArray:self.numberOfAdmins];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSDictionary *jsonForJam = [self.combinedStrings objectAtIndex:indexPath.row];
return cell;
}
I would like to sort the result displayed in the tableView in descending order, how can I do such thing ?
I've already tried to use that but it was not helpful : Best way to sort an NSArray of NSDictionary objects?
A solution based on the sample code I provided will be very helpful.
You can use the following code for sorting array of strings.
self.combinedStrings = [[[self.combinedStrings sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)] reverseObjectEnumerator] allObjects];
or
NSSortDescriptor* sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:nil ascending:NO selector:#selector(localizedCompare:)];
self.combinedStrings = [self.combinedStrings sortedArrayUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]];
A) you can implement your own custom sorting method (that still ends up using iOS sorting methods in the end) because the NSDictionary complicates matters.
or
B) create an object (its purpose is to hold all the info in the dictionaries) then store the custom objects in the array, then sort it like this or in any matter you wish:
self.combinedStrings = [self.combinedStrings sortedArrayUsingComparator:^(id obj1, id obj2)
{
Client *client1 = (Client*)obj1;
Client *client2 = (Client*)obj2;
if([client1.firstName isEqualToString:client2.firstName])
return [client1.lastName compare:client2.lastName];
else
return [client1.firstName compare:client2.firstName];
}];
Here "Client" would be the custom object and the data is stored in properties for easy access.
Hope this helps
I have a UITableView that adds information from a Core Data the following way:
The "Category" for various names is added at the header
The names that correspond to the Category should be loaded in cells beneath the Category
Right now I have the header loading the right name and the right number of sections and rows being added. However - the results from Core Data are being returned as an NSSet, so I can't add each name to the cells based on the indexPath.row.
Any suggestions? For reference:
cell.textLabel.text = [NSString stringWithFormat:#"%#",[[[dataToUse objectAtIndex:indexPath.section]valueForKey:#"heldBy"]valueForKey:#"name"]];
Returns the appropriate set of names to each cell of the appropriate section, but it returns the ENTIRE set to each cell. I just want each name to be added based on which row it's a part of. I can solve this by converting the NSSet to an Array, but since there are multiple sets being created (because there are multiple categories) I don't see how I can do this.
EDIT: I fixed my problem by doing the following, but I'm still interested to know what the best thing to do would have been.
NSSet *daset = [[dataToUse objectAtIndex:indexPath.section]valueForKey:#"heldBy"];
NSMutableArray *addToLabel = [[NSMutableArray alloc]init];
int i = 0;
for(NSSet *contact in daset) {
[addToLabel insertObject:[contact valueForKey:#"name"] atIndex:i];
NSLog(#"%#",addToLabel);
i++;
}
cell.textLabel.text = [NSString stringWithFormat:#"%#",[addToLabel objectAtIndex:indexPath.row]];
Use an NSFetchedResultsController
It's designed to do exactly what you're looking for. For straight forward cases like yours, where you just need your data organized into sections based on your model relationships it will offload a lot of weight off your shoulders by automatically managing the fetching, editing, caching etc. You can find a nice tutorial here and of course the official documentation here.
To convert from NSSet->NSArray you need to sort the set with a NSSortDescriptior. Something like:
NSSortDescriptor *sort=[[NSSortDescriptor alloc] initWithKey:#"nm" ascending:YES selector:#selector(localizedCaseInsensitiveCompare:)];
NSArray *arr = [[myset allObjects] sortedArrayUsingDescriptors:[NSArray arrayWithObject:sort]];
NSSortDescriptor *sort=[[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES selector:#selector(localizedCaseInsensitiveCompare:)];
NSArray *arr = [[myset allObjects] sortedArrayUsingDescriptors:[NSArray arrayWithObject:sort]];
"nsset object" *nssetObject =[arr objectAtIndex:indexPath.row];
cell.textLabel.text = [NSString stringWithFormat:#"%#",nssetObject.name];
I have an NSMutableDictionary of websites
[dictionaryOfSites setObject:#"http://www.example.com" forKey:#"Example.com"];
[dictionaryOfSites setObject:#"http://www.site1.com" forKey:#"Site1"];
[dictionaryOfSites setObject:#"http://www.apple.com" forKey:#"Apple"];
I know you can't sort a dictionary. But I've read that other people have used an NSMutableArray as the key and the array can be sorted.
So if I setup a new array
[[arrayKey alloc] initWithObjects:#"Example.com", #"Site1", #"Apple", nil];
I would then modify my first snippet to
[dictionaryOfSites setObject:#"http://www.example.com" forKey:[arrayForKey objectAtIndex:0]];
[dictionaryOfSites setObject:#"http://www.site1.com" forKey:[arrayForKey objectAtIndex:1]];
[dictionaryOfSites setObject:#"http://www.apple.com" forKey:[arrayForKey objectAtIndex:2]];
In this simple problem, I had 3 sites so I "hard" coded it. How would I do the same thing if my list of sites was 100? How would the order of the sites be maintained?
If I sort my array
[arrayKey sortUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
Wouldn't index 2 become index 0? If it becomes index 0 then you can see the dictionaryOfSites has the wrong label with the URL.
So you can use a custom class (as I mentioned above in my comment), or better yet use an NSDictionary to store the values as MarkM suggested.
EDIT: "i don't have to maintain a dictionary. its a new app from the ground up."
Since you don't need to start with one big dictionary like you posted, it would be better to just store individual dictionary objects for each site in an array and not have to worry about the conversion.
// Setup the initial array
NSMutableArray *arrayOfSites = [NSMutableArray new];
[arrayOfSites addObject:#{#"Name" : #"Example.com",
#"URL" : #"http://www.example.com"}];
[arrayOfSites addObject:#{#"Name" : #"Site1",
#"URL" : #"http://www.site1.com"}];
[arrayOfSites addObject:#{#"Name" : #"Apple",
#"URL" : #"http://www.apple.com"}];
// At this point, arrayOfSites contains a dictionary object for each site.
// Each dictionary contains two keys: Name and URL with the appropriate objects.
// Now we just need to sort the array by the Name key in the dictionaries:
NSSortDescriptor *descriptor = [[NSSortDescriptor alloc] initWithKey:#"Name" ascending:YES];
[arrayOfSites sortUsingDescriptors:[NSArray arrayWithObjects:descriptor, nil]];
NSLog(#"%#", arrayOfSites);
Results:
2013-05-07 18:19:08.386 Testing App[75712:11f03] (
{
Name = Apple;
URL = "http://www.apple.com";
},
{
Name = "Example.com";
URL = "http://www.example.com";
},
{
Name = Site1;
URL = "http://www.site1.com";
} )
To access the data, you would use:
NSString *name = [[arrayOfSites objectAtIndex:indexPath.row] objectForKey:#"Name"];
Note that arrayOfSites should be a declared property of your class so that you can access it from different methods.
What you need to do is store your NSDictionary objects in the array and then access a value from that array to do the sorting if you wish. You don't actually store a new string for the sorting. You just check the value of a certain key in the dictionary at the index in the array.
Here is a good source for sorting an array of dictionaries
I'm sure this question has been asked before, but I couldn't find anything in the search.
I have a list of city names which can change depending on the country selected. I would like to order these alpabetically and have them show up in sections based on the first letter (like the contacts list).
How can I accomplish this?? Do I have to manually create all the sections or is there a built-in way?
Thanks.
Sort your datasource to be in the order you wish, then reload the tableview.
Try making an NSDictionary of NSMutableArrays. Take the first letter of each city, toUpper it. Then insert your city into the array for the letter k.
Number of sections = [[NSDictionary allKeys] count]
NSArray* cityNames = [NSArray arrayWithObject: #"Detroit"];
NSMutableDictionary* dataSource = [[NSMutableDictionary alloc] init];
for( NSString* city_name in cityNames){
NSString* key = [[city_name substringToIndex:1] uppercaseString];
if( ![dataSource objectForKey: key] ){
[dataSource setObject:[NSMutableArray array] forKey:key];
}
[[dataSource objectForKey: key] addObject: city_name];
}
If you need more help setting up your datasource with your tableview say so in a comment and I'll help you more. Cheers
Yes, you have to manually create all sections. And it would be convenient to store sections in NSDictionary. To have lettered section indicies just return an array of them in sectionIndexTitlesForTableView: