I have a table view that pulls data from my database, and displays it in my tableview cells. The values returned are:
This is the messages data (
{
body = "13-10-2017 12:12";
name = "Leeya";
},
{
body = "09-10-2017 19:37";
name = Leeya;
},
{
body = "test";
name = "Jessica";
}
That said, when my dictionary comes back with multiple arrays containing the same 'name' value (as it is in this case, 'Leeya'), I only want my tableview to display the first of these arrays (one cell), much like in a chat app when you see a list of conversations.
The code I have currently accomplishes this, but it literally only returns ONE cell and ONE array, populated by the most recently posted data. E.g. tableview returns cell:
LEEYA
When it should return:
LEEYA
JESSICA
See my code below. I assume this is because the line
NSString *nodeTitle = self.messages[0][#"name"];
is always equal to one username, and thus it returns one value.
So my question is: How can I make that line function so that, "If 'name' value in dictionary self.messages appears more than once, only show the first value" ? Or in other words: If 'name' value is equal to 'name' in arrays that follow, only show the first?
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *nodeTitle = self.messages[0][#"name"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"name == %#", nodeTitle];
NSArray *filteredArray = [self.messages filteredArrayUsingPredicate:predicate];
id firstFoundObject = nil;
firstFoundObject = filteredArray.count > 0 ? filteredArray.firstObject : nil;
NSDictionary *firstObjects = firstFoundObject;
NSLog(#"The first objects are %#", firstObjects);
static NSString *PointsTableIdentifier = #"MyMessagesCell";
MyMessagesCell *cell = (MyMessagesCell *)[tableView dequeueReusableCellWithIdentifier:PointsTableIdentifier];
if (cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"MyMessagesCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
I hope I explained this clearly. Thanks!
I would suggest not trying to do this in cellForRowAtIndexPath.
Instead, when you first get your data from the server, write a method that takes the input array and filters it to remove duplicates. Something like this:
- (NSArray *) UniqueEntriesForArray: (NSArray *) sourceArray {
NSMutableArray *results = [NSMutableArray array];
NSMutableSet *uniqueStrings = [NSMutableSet new];
for (NSDictionary *element in sourceArray) {
NSString *name = element[#"name"];
if (![uniqueStrings contains: name]) {
[uniqueStrings addObject: name];
[results addObject: element];
}
}
return [results copy];
}
(My Objective-C is getting a little rusty so that might need some adjustment. It would also be cleaner if you used the new typed array syntax for Objective-C, but I don't remember that off the top of my head.)
- (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
Thanks to a more expert user, I have implemented a method to sort core data objects inside a table view section. The sorting method inside the section is different to the sorting method of the table view. The latest is done by a NSFetchedResultsController. But now I don't know how to get the values from the sorted NSArray.
This is the method that sorts the core data objects inside a section:
- (NSArray*)sortedSectionForIndex:(NSInteger)index
{
NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:#"priority" ascending:YES];
id section = [[self fetchedResultsController] sections][index];
NSLog(#"INDEX********* = %ld", (long)index);
NSArray *objects = [section objects];
NSArray *sorted = [objects sortedArrayUsingDescriptors:#[sort]];
return sorted;
}
And this is the code added to the cellForRowAtIndexPat method to get the NSArray from the sortedSectionForIndex method:
NSArray *sorted = [self sortedSectionForIndex:[indexPath section]];
id object = [sorted objectAtIndex:[indexPath row]];
NSLog(#"OBJECTV= %#",object);
Now, I would need to know how to show the values from the array to be put as cell.textlabel:
A second question is that I don't know if getting the core data object from the array will change the way to handle them for the rest of the methods. I mean, before implementing this sorting method, when the user clicks on a row, a detail view from the selected object was shown. Now getting the objects from an array and not from the NSFetchedResultsController, I am not sure if it will continue working as before.
I would change the line
id object = . . .
To
NSManagedObject *object = . . .
And then
cell.textLabel = [object valueForKey:#"todoName"];
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 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.