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:
Related
I have a UITableView and am displaying contents from my NSMutableArray. Following is array format
(
{
Name = "ANS";
VersionNo = 6;
},
{
Name = "O-Hydro";
Version = 6;
},
{
Name = "ANS";
Version = 6;
},
{
Name = "ANTIChorosAnticholinergic";
Version = 6;
}
)
From this I need to display only unique "Name" (like in this I can see 2 "ANS" I need only one).
How can I do this in iOS?
I tried following but its not working
uniqueArray= [[NSMutableSet setWithArray: groupDetails] allObjects];
but in this way I can do only for NSArray not NSMutableArray.
Pls help me
You can use following line of code to convert your NSArray to NSMutableArray,
NSArray *uniqueArray= [[NSMutableSet setWithArray:groupDetails] allObjects];
NSMutableArray *myMutableArray = [[NSMutableArray alloc] initWithArray:uniqueArray];
You could simply add mutableCopy.
But wait, before you do it. Arrays and sets have two differences:
Arrays can contain duplicates, sets cannot.
Arrays are ordered, sets are not.
So doing what you are doing, you lose the duplicates (intentionally), but the order, too (probably not intentionally).
I do not know, whether this is important for you, but for other readers it might be. So it is the better approach to do that with NSOrderedSet instead of NSSet:
NSOrderedSet *uniqueList = [NSOrderedSet orderedSetWithArray:array];
In many cases an ordered set is exactly what you want. (Probably it has been from the very beginning and the usage of NSArray was wrong. But sometimes you get an array.) If you really want an array at the end of the day, you can reconvert it:
array = [uniqueList.array mutableCopy];
If you just want an array of unique name values, you can use #distinctUnionOfObjects with valueForKeyPath -
NSArray *uniqueArray=[groupDetails valueForKeyPath:#"#distinctUnionOfObjects.name"];
But if you want the array to contain the dictionaries that correspond to the unique names then you need to do a little more work -
NSMutableArray *uniqueArray=[NSMutableArray new];
NSMutableSet *nameSet=[NSMutableSet new];
for (NSDictionary *dict in groupDetails) {
NSString *name=dict[#"name"];
if (![nameSet containsObject:name]) {
[uniqueArray addObject:dict];
[nameSet addObject:name];
}
}
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 would like to create an NSArray of NSStrings that is a concatinated value of two elements from a NSDictionary found in my original NSArray. A bit complicated I know but I feel I am almost halfway there and will describe where I am up too.
So I have a NSArray of NSDictionaries, the NSDictionaries look like this -
NAME
NICK NAME
YEAR
LEGAL
I would like to take the Name and Nick name values of each dictionary and form them into an NSArray of NSStrings that look like this
Name (Nick Name)
but I am at abit of a loss on how to do this properly I have gotten as far as a For loop lol
for (int i = [dataArrayOfDictionaries count]; i <= [dataArrayOfDictionaries count]; i++) {
}
My main questions are, how do I access these Name and Nick Name element of each NSDictionary object of the array.
Then how do I put them into a formatted string like the example above and put them into their own NSArray
any help would be greatly appreciated.
So if I understand correctly, you want to grab the name and nick name from each dictionary that you have stored in an array and then combine the two and put them in a new array? Try something like this:
NSMutableArray *yourNewMutableArray = [[NSMutableArray alloc]init];
for(NSDictionary *dict in dataArrayOfDictionaries)
{
NSString *realName = [dict objectForKey:#"NAME"];
NSString *nickName = [dict objectForKey:#"NICK NAME"];
NSString *combined = [[[realName stringByAppendingString:#" ("]stringByAppendingString:nickName]stringByAppendingString:#")"];
[yourNewMutableArray addObject:combined];
}
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.
Let's say I have an NSArray of NSDictionaries that is 10 elements long. I want to create a second NSArray with the values for a single key on each dictionary. The best way I can figure to do this is:
NSMutableArray *nameArray = [[NSMutableArray alloc] initWithCapacity:[array count]];
for (NSDictionary *p in array) {
[nameArray addObject:[p objectForKey:#"name"]];
}
self.my_new_array = array;
[array release];
[nameArray release];
}
But in theory, I should be able to get away with not using a mutable array and using a counter in conjunction with [nameArray addObjectAtIndex:count], because the new list should be exactly as long as the old list. Please note that I am NOT trying to filter for a subset of the original array, but make a new array with exactly the same number of elements, just with values dredged up from the some arbitrary attribute of each element in the array.
In python one could solve this problem like this:
new_list = [p['name'] for p in old_list]
or if you were a masochist, like this:
new_list = map(lambda p: p['name'], old_list)
Having to be slightly more explicit in objective-c makes me wonder if there is an accepted common way of handling these situations.
In this particular case Cocoa is not outdone in succinctness :)
NSArray *newArray = [array valueForKey:#"name"];
From the NSArray documentation:
valueForKey:
Returns an array containing the
results of invoking valueForKey: using
key on each of the receiver's objects.