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];
Related
I have a NSArray self.objects which is an array of users.
I'd like to section off the usernames alphabetically into a sectioned UITableView.
I took a shot at doing this:
NSDictionary *usernameDictionary = [NSDictionary dictionaryWithObject:[self.objects valueForKey:#"username"] forKey:#"usernames"];
I'm not sure where to go.
UPDATE:
NSMutableSet *mySet = [[NSMutableSet alloc] init];
for (NSString *s in [self.objects valueForKey:#"username"] )
{
if ( s.length > 0 )
[mySet addObject:[s substringToIndex:1]];
}
NSArray *indexArray = [[mySet allObjects] sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
I've got the section headers to read the letters, and on the side, only issue im having is that each section has the entire array in it. and not specific towards the letter in order
Having a hard time using appcoda's example of numberofrowsinsection:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
NSString *sectionTitle = [animalSectionTitles objectAtIndex:section];
NSArray *sectionAnimals = [animals objectForKey:sectionTitle];
return [sectionAnimals count];
}
NSArray *sortedArray = [self.objects sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
Now you can create a mutable dictionary where it will add objects as per section i.e one key will contain array of those usernames with same alphabet and the key can be first character of first username with change in alphabet.
I have an array which contains multiple Dictionaries each one with 3 keys (#"date", #"username", #"text").
What I want to check for, is whether the same user (#"username") exists in more than one dictionary in that Array. And, if she does, combine the text for those "duplicates" into one dictionary.
I have considered this answer to check for duplicates and this one
but I cannot figure out how to combine these two.
Jumping in here because although I think you should work on the code yourself first, I think Miro's answer is more complicated than the issue requires and though I like the idea of using predicates in Greg's answer, here's a 3rd solution that (1) wouldn't require you to change your data structure and (2) references the necessary loops...
The way I'd do it: Create an NSMutableArray then start adding the usernames in order. If the NSMutableArray already contains the username though, don't add another instance of the username, but instead merge the dictionary info.
ex.
// Note: I'm calling your array of user dictionaries userArray.
// Create a username array to store the usernames and check for duplicates
NSMutableArray *usernames = [[NSMutableArray alloc] init];
// Create a new userArray to store the updated dictionary info, merged
// entries et. al.
NSMutableArray *newUserArray = [[NSMutableArray alloc] init];
// Go through the array of user dictionaries
for (NSDictionary *userDict in userArray) {
// If the usernames array doesn't already contain the username,
// add it to both the usernames array and the newUserArray as is
if (![usernames containsObject:[userDict objectForKey:#"username"]]) {
[usernames addObject:[userDict objectForKey:#"username"]];
[newUserArray addObject:userDict];
}
// Otherwise, merge the userArray entries
else {
// Get a mutable copy of the dictionary entry at the first instance
// with this username
int indexOfFirstInstance = [usernames indexOfObject:[userDict objectForKey:#"username"]];
NSMutableDictionary *entry = [[newUserArray objectAtIndex:indexOfFirstInstance] mutableCopy];
// Then combine the "text" or whatever other values you wanted to combine
// by replacing the "text" value with the combined text.
// (I've done so with a comma, but you could also store the value in an array)
[entry setValue:[[entry objectForKey:#"text"] stringByAppendingString:[NSString stringWithFormat:#", %#", [userDict objectForKey:#"text"]]] forKey:#"text"];
// Then replace this newly merged dictionary with the one at the
// first instance
[newUserArray replaceObjectAtIndex:indexOfFirstInstance withObject:entry];
}
}
Maybe something like this [untested] example? Loop through, maintain a hash of existing items, and if a duplicate is found then combine with existing and remove.
NSMutableArray main; // this should exist, with content
NSMutableDictionary *hash = [[NSMutableDictionary alloc] init];
// loop through, backwards, as we're attempting to modify array in place (risky)
for(int i = [main count] - 1; i >= 0; i--){
// check for existing
if(hash[main[i][#"username"]] != nil){
int existingIdx = [hash[main[i][#"username"]] integerValue]; // get existing location
main[existingIdx][#"text"] = [main[existingIdx][#"text"] stringByAppendingString:main[i][#"text"]]; // "combine text" .. or however you'd like to
[main removeObjectAtIndex:i]; // remove duplicate
} else {
[hash setValue:[[NSNumber alloc] initWithInt:i] forKey:main[i][#"username"]]; // mark existance, with location
}
}
If you use NSMutableDictionary, NSMutableArray and NSMutableString you can do it with predicate like that:
NSMutableDictionary *d1 = [#{#"username": #"Greg", #"text" : [#"text 1" mutableCopy]} mutableCopy];
NSMutableDictionary *d2 = [#{#"username": #"Greg", #"text" : [#"text 2" mutableCopy]} mutableCopy];
NSMutableDictionary *d3 = [#{#"username": #"John", #"text" : [#"text 3" mutableCopy]} mutableCopy];
NSMutableArray *array = [#[d1, d2, d3] mutableCopy];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"username = %#", #"Greg"];
NSArray *filterArray = [array filteredArrayUsingPredicate:predicate];
NSMutableDictionary * firstDict = filterArray[0];
for (NSDictionary *d in filterArray)
{
if (firstDict != d)
{
[firstDict[#"text"] appendString:d[#"text"]];
[array removeObject:d];
}
}
Hope someone could help me with that :
I'm using a NSDictionary to fill a UITableView.
Its model is like [key:userID => value:userName].
The tableView is only filled with userName but when clicked, it has to send the userID related.
The problem comes when I want to filter the UITable. I only found the way to filter a Dictionary by transforming it into NSArray (using Predicate) but it make me loose the relation between userNames and userIDs.
A solution would be to filter the initial NSDictionary to get a filtered NSDictionary (with still the relational key/value), but I don't know how to do that. I only found solutions to get Arrays.
How could I do that, or is there a better solution to do it?
There is a much better solution, François.
Create, from your NSDictionary (I will call it here myDictionary), an NSArray like this (declare it in your interface file):
NSArray *arrayForTableView;
Then, just after you load your NSDictionary, do the following:
arrayForTableView = [myDictionary allKeys]; // so you will have an array of all UserID's
Now, in your tableView: cellForRowAtIndexPath: method, you can do it like this:
cell.textLabel.text = [myDictionary objectForKey:[arraForTableView objectAtIndex:indexPath.row]];
And then, when you will want to pass the userID when the user selects the cell, in your tableView: didSelectRowAtIndexPath: you just do it this way:
id userIDSelected = [arraForTableView objectAtIndex:indexPath.row];
Then, when you want to filter the array according to the search, you can simply recreate your arrayForTableView, by "scanning" your NSDictionary this way:
NSString *typedString;
NSMutableArray *arrayFiltered = [NSMutableArray array];
for (int i = 0; i < [[myDictionary allKeys] count]; i++)
{
if ([[myDictionary objectForKey:[[myDictionary allKeys] objectAtIndex:i]] rangeOfString:typedString].location != NSNotFound)
{
[arrayFiltered addObject:[[myDictionary allKeys] objectAtIndex:i]];
}
}
arrayForTableView = arrayFiltered;
This way, you won't even need to change your UITableView dataSource and delegate methods.
You can do following to get value(userID) for selected key(userName) :
//iterate through whole dictionary
for(id key in yourNSDictionary)
{
// if key is the userName clicked
if([key isEqualToString:selectedUserName])
{
//userID for clicked userName
int userID = [yourNSDictionary objectForKey:#selectedUserName];
}
}
you're using an NSDictionary to populate an UITableView and this UITableView is only filled with the username which you get by doing
[dictionary objectForKey#"userID"];
a NSDictionary has two functions allkeys and allValues
NSArray* allUserID = [dictionary allKeys];
NSArray* allUserNames = [dictionary allValues];
this is a parallel arrays so that the index of one array, runs parallel with it's associated array.
Each cell of the table cell could also be a custom class that holds a reference to it's own id and username, this will allow you to only pass the cell and have it's data.
you can read about those functions in the NSDictionary documentation
https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSDictionary_Class/Reference/Reference.html
i would recommend creating an NSArray or NSMutableArray with NSDictionary values - UITableViews are meant to be driven by arrays, where the array index matches the row number. Then you can easily create a custom filter for the array of dictionaries which take into account your data structure. Your code might include parts of this sample code:
NSString *idKey = #"userId";
NSString *nameKey = #"userName";
NSArray *arr = #[
#{
idKey : #(24),
nameKey : #"Oil Can Henry"
},
#{
idKey : #(32),
nameKey : #"Doctor Eggman"
},
#{
idKey : #(523),
nameKey : #"Sparticus"
},
];
NSString *searchTerm = #"Spar";
NSArray *newArray = [arr filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
return [evaluatedObject[nameKey] hasPrefix:searchTerm];
}]];
Advantages:
a single data structure to represent all your data
inherent, deterministic ordering
support for NSPredicate filtering
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.
NSMutableDictionary *expense_ArrContents = [[NSMutableDictionary alloc]init];
for (int i = 1; i<=4; i++) {
NSMutableArray *current_row = [NSMutableArray arrayWithObjects:#"payer_id",#"Expense_Type_id",#"Category_Id",#"SubCategory_Id",nil];
[expense_ArrContents setObject:current_row forKey: [NSNumber numberWithInt:i]];
}
NSArray *newArray = [expense_ArrContents allKeysForObject:#"payer_id"];
NSLog(#"%#",[newArray description]);
i want to get the list of key values containing the particular object which is in the array of values stored in nsmutabledictionary for a particular key.
In the line where you get all the keys ([expense_ArrContents allKeysForObject:#"payer_id"];) you actually get keys for an object that is not in any of the array's items. This #"player_id" is different object than the #"player_id" you added in current_row. In fact, maybe all of your rows have different #"player_id" objects (except if the compiler has made some optimization - maybe it threats that same string literal as one object instead of creating new object for each iteration).
Try creating an NSString object for the #"player_id" which you add to the current_row and then get all the keys for that same object:
NSString* playerId = #"player_id";
for(){
NSMutableArray *current_row = [NSMutableArray arrayWithObjects: playerId,...];
...
}
NSArray *newArray = [expense_ArrContents allKeysForObject:playerId];
Your NSArray *newArray = [expense_ArrContents allKeysForObject:#"payer_id"]; will not return any value because in expense_ArrContents there is no such key(#"payer_id"), instead there are keys like 1,2,3 etc.What is your requirement?Want to see what all keys are there in expense_ArrContents just log
NSArray*keys=[expense_ArrContents allKeys];
Try this :
NSMutableArray *array_key=[[NSMutableArray alloc]init];
for (NSString *key in expense_ArrContents) {
if ([[expense_ArrContents objectForKey:key] containsObject:#"payer_id"]) {
[array_key addObject:key];
}
}