All,
I have about 3000 words with definitions that I am loading into a TableView. Right now, it's just a sorted list of words, sans the sections because I haven't added them yet.
I need to add sections to my TableView data (A,B,C ...) and there seems to be several ways to do this so before I jump into this I am looking for some confirmation or correction if I am going down the wrong rabbit hole.
Currently the data that the TableView reads is stored as objects in an NSMutableArray per this code:
//AppDelegate.m
- (void)applicationDidFinishLaunching:(UIApplication *)application
{
//...
NSMutableArray *wordArray = [[NSMutableArray alloc] init];
//Loop through result set from DB and populate objects
while([rs next]){
[wordArray addObject:[Word wordWith:[rs stringForColumn:#"word"]
Definition:[rs stringForColumn:#"definition"]
SectionIndex:[rs stringForColumn:#"sectionIndex"]]];
}
MainViewController *mainViewController =
[[MainViewController alloc] initWithNibName:#"MainView" bundle:nil];
mainViewController.listContent = wordArray;
//...
}
Each object has a section index value ([A-Z0-9]) so I already know which section each word goes in, I know what the sections need to be and I can easily derive a count of objects for each section. All the words have been sorted via SQL before the NSMutableArray was populated so that's already handled.
Can I create multiple sections with the one NSMutableArray or do I need to do something different?
Thanks
You could store your words into arrays inside a NSDictionary holding keys for each letter.
Number of sections would return
[[dictionary allKeys] count];
Title for section
NSArray * keys = [dictionary allKeys];
[keys objectAtIndex:sectionIdx]
Number of rows in section would return
NSArray * keys = [dictionary allKeys];
[(NSArray *)[dictionary objectForKey:[keys objectAtIndex:sectionIdx]] count];
Each word would be
NSArray * keys = [dictionary allKeys];
[(NSArray *)[dictionary objectForKey:[keys objectAtIndex:sectionIdx]] objectAtIndex:indexPath.row];
I have found that you sometimes want to add sorting to your lists and then, another approach might be interesting. Put all your models (Word's in your example) in a dictionary with some unique value of the model as the key.
Implement a sorting method, that you run every time the underlying dictionary changes. The sorting method will use e.g. keysSortedByValueUsingComparator on the dictionary and supply a different blocks for different sort orders. Let the sorting method create section arrays and add keys in the arrays that corresponds to the keys in the dictionary.
You do not store anything twice and you get different sort orders by just providing different sort blocks (that can look at any properties of your model class).
Related
I want to implement "expanded" behaviour on click on table view headers. For that, i have NSDictionary, which is have all data in form key -> array of values.
What i want is, create other dictionary, copy of initial, and remove all data in arrays inside it. So, in initial loading, our table will look like "closed" headers, after tap on each one, it will collaps and show values corresponding to given key. After tap on header aggain, it will "close" and hide values.
So, basically i want to:
1) enumerate through an NSDictionary and remove all data from array (or create new empty arrays)
2) dynamically add/remove data for given key
Is there easy way to achieve that?
How about this:
NSMutableDictionary *newDict = [NSMutableDictionary new];
for id aKey in tableDict {
newDict[aKey] = [NSMutableArray new];
}
tableDict = newDict;
[tableView reloadData];
Edit:
To clear a single key
tableDict[specificKey] = [NSMutableArray new];
To copy the array from one key into another:
tableDict[specificKey] = [((NSMutableArray *)tableDict[otherKey]) mutableCopy];
I am trying to check if the NSMutableArray has a specific object, before adding the object to it, if exists then don't add.
i looked over many posts explaining how to do this, managed to implement it like this, but it always gives me that the object "doesn't exist", though i already added it !
//get row details into FieldLables Object
AllItemsFieldNames *FieldLabels = feedItems[row];
// object to hold single row detailes
AllItemsFieldNames *SelectedRowDetails = [[AllItemsFieldNames alloc] init];
SelectedRowDetails.item_name = FieldLabels.item_name;
//SelectedRowDetails.item_img = FieldLabels.item_img;
SelectedRowDetails.item_price = FieldLabels.item_price;
//NSLog(#"item has been added %#", SelectedRowDetails.item_name);
//NSLog(#"shopcartLength %lu", (unsigned long)SelectedFieldsNames.count);
if([SelectedFieldsNames containsObject:SelectedRowDetails])
{
NSLog(#"Already Exists!");
}
else
{
NSLog(#"Doesn't Exist!");
[SelectedFieldsNames addObject:SelectedRowDetails];
}
I can display all object from the NSMutableArray into a table, what i need to do in the above code is stop the addition of duplicate objects.
The first method listed on the NSArray documentation under the section "querying an array" is containsObject:. If it's not working, that suggests that your implementation of isEqual: is not correct. Make sure you follow the note in the documentation:
If two objects are equal, they must have the same hash value. This
last point is particularly important if you define isEqual: in a
subclass and intend to put instances of that subclass into a
collection. Make sure you also define hash in your subclass.
You might also consider using an NSSet since you can't add duplicates to that. Of course, this would also require a working version of isEqual:.
Sets are composed of unique elements, so this serves as a convenient way to remove all duplicates in an array.
here some sample,
NSMutableArray*array=[[NSMutableArray alloc]initWithObjects:#"1",#"2",#"3",#"4", nil];
[array addObject:#"4"];
NSMutableSet*chk=[[NSMutableSet alloc ]initWithArray:array]; //finally initialize NSMutableArray to NSMutableSet
array= [[NSMutableArray alloc] initWithArray:[[chk allObjects] sortedArrayUsingSelector:#selector(compare:)]]; //after assign NSMutableSet to your NSMutableArray and sort your array,because sets are unordered.
NSLog(#"%#",array);//1,2,3,4
I have an NSFetchResultsController which returns ManagedObjects arranged by sections.
The sections are the NSString name of the Category objects which my Feed objects share in common:
Now, in some situations I want to obtain the Category itself from the section:
NSString *sortKey = #"category.name";
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"Feed"];
NSSortDescriptor *sortDescriptorCategory = [NSSortDescriptor sortDescriptorWithKey:sortKey ascending:asc];
At this moment, I do this:
Category *cat = ((Feed *)[[[self.fetchedResultsController sections][section] objects] objectAtIndex:0]).category;
Which is IMHO quite ugly. Especially as it forbids me to have empty sections which however might happen if I create new sections into which I want to move existing Feeds to.
So my question is : how do I access the Category object which defines the sections in my Feed list?
Also, how could I efficiently gather a list of all the sections in an NSSet?
Thanks in advance for your help.
Your method to retrieve the section object is OK, you should just include sanity checks.
NSArray *feeds = [self.fetchedResultsController.sections[section] objects];
Feed *aFeed = [feeds anyObject];
return aFeed ? aFeed.category : nil;
A list of all sections as a NSSet:
[NSSet setWithArray:self.fetchedResultsController.sections];
If you want to use the category entity extensively in your section headers, maybe it is better to change your setup: fetch the Categories rather than the feeds, and change the table view datasource methods to reflect this setup, e.g.
// number of rows for section
Category *category = self.fetchedResultsController.fetchedObjects[section];
return category.feeds.count;
You will need to introduce a sorting criterion to change the feeds set into an array for the rows of a section.
and thanks for looking...
I'm not even sure how to phrase this question, let alone search for the answer... I have tried, honestly, so all help needed!
It's probably pretty simple as I am sure this is a pattern that happens all the time:
I have an entity in my model (MainLevel*) that has a relationship to itself.
The entity is for levels of a law, and the only requirement is that each law has at least one (the top) level. Beyond that the number of sublevels is, technically, infinite (but in reality about 5 normally and probably no more than 8-10 at most). As might be expected each child level has only one parent (MainLevel.parentLevel) and any parent can have multiple (or zero) children (NSSet *childLevels).
What I would like to do is to get all the structure of this relationship to put in a UITableView or a collectionView.
I have a recursive function as follows:
- (NSDictionary *)getStructureOfLawForLevel:(MainLevel *)level
{
NSMutableDictionary *mutableDict = [[NSMutableDictionary alloc]initWithCapacity:50];
MainLevel *currentLevel = level;
[mutableDict setObject:currentLevel.shortName forKey:#"name"];
if (level.childLevels) {
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"order" ascending:YES];
NSArray *sortdescriptors = #[sortDescriptor];
NSArray *children = [currentLevel.childLevels sortedArrayUsingDescriptors:sortdescriptors];
for (MainLevel *childLevel in children)
{
NSDictionary *dict = [self getStructureOfLawForLevel:childLevel];
[mutableDict setObject:dict forKey:#"sublevels"];
}
}
return [mutableDict copy];
}
Then in viewWillAppear: I have this:
self.structure = [self getStructureOfLawForLevel:self.selectedLevel]
With this I hope I am on the right lines...(untested due to another issue I am sorting right now).
I still cant figure out how to configure a UITableView or a UICollectionView from this though. I mean I am sure I can do it by adding a counter or two and getting the number of lines, and sections, that way. It just seems way, way overcomplicated and I am certain there must be a more obvious method I am just not seeing...
The only criteria for the data is that it must be ordered by the .order attribute of the entity instance, and that is not unique. I mean, for example, each childLevel can have a childLevel with order number 1. It is the order in THAT parent level.
Sorry if this has been asked a thousand times. I have tried to search for an answer but nothing seems to fint the search terms I am using.
I am not doing anything with this data except putting on screen, no editing, adding, deleting... Not sure if that is relevant.
Edit for clarity...
I am not looking to do a drill-down type table view. I want a snapshot of the whole structure in one view, and then I may need to drill down from that (using relationships to other entities in the model).
EDIT FOR MY SOLUTION
Here's what I ended up doing...
- (NSArray *)getStructureAsArrayForLevel:(MainLevel *)child
{
NSMutableArray *thisChildAndChildren = [[NSMutableArray alloc]initWithCapacity:2];
[thisChildAndChildren addObject:child];
if (child.childLevels)
{
// children exist
// sort the children into order
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"order" ascending:YES];
NSArray *sortdescriptors = #[sortDescriptor];
NSArray *children = [child.childLevels sortedArrayUsingDescriptors:sortdescriptors];
// get an array for each child via recursion
for (MainLevel *child in children)
{
[thisChildAndChildren addObject:[self getStructureAsArrayForLevel:child]];
}
}
return [thisChildAndChildren copy];
}
Then I am using similar recursive function to convert the array to NSAttributedString and display in textView.
I really DO NOT like recursion. I don't know why but I find it SOOOOOOO hard to get my head around the logic, and when it's done it seems so obvious... Go figure!
Thanks to everyone for suggestions, help etc...
If you can use a 3rd-party controller, take a look at TLIndexPathTools. It handles tree structures. For example, try running the Outline example project.
Your view controller would look something like this (not much to it):
#import "TLTableViewController.h"
#interface TableViewController : TLTableViewController
#end
#import "TableViewController.h"
#import "TLIndexPathTreeItem.h"
#implementation TableViewController
- (void)viewDidLoad
{
[super viewDidLoad];
MainLevel *topLevel = nil;//get the top level object here
TLIndexPathTreeItem *topItem = [self treeItemForLevel:topLevel depth:0];
self.indexPathController.dataModel = [[TLTreeDataModel alloc] initWithTreeItems:#[topItem] collapsedNodeIdentifiers:nil];
}
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
// customize cell configuration here
TLIndexPathTreeItem *item = [self.dataModel itemAtIndexPath:indexPath];
MainLevel *level = item.data;
cell.textLabel.text = [level description];
}
- (TLIndexPathTreeItem *)treeItemForLevel:(MainLevel *)level depth:(NSInteger)depth
{
NSMutableArray *childItems = [NSMutableArray arrayWithCapacity:50];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"order" ascending:YES];
NSArray *sortdescriptors = #[sortDescriptor];
NSArray *children = [level.childLevels sortedArrayUsingDescriptors:sortdescriptors];
for (MainLevel *child in children) {
TLIndexPathTreeItem *childItem = [self treeItemForLevel:child depth:depth + 1];
[childItems addObject:childItem];
}
//set identifier to some unique identifier, if one exists. Otherwise, the item itself
//will be used as the identifier
id identifier = nil;
//set cell identifier based on depth. This can be set to something unique for each
//depth, or set to a constant value. If nil, the value "Cell" is assumed.
NSString *cellIdentifier = [NSString stringWithFormat:#"Cell%d", depth];
//pass "level" to the data argument. Or pass any other data, e.g. include the depth #[#(depth), level]
TLIndexPathTreeItem *item = [[TLIndexPathTreeItem alloc] initWithIdentifier:identifier sectionName:nil cellIdentifier:cellIdentifier data:level andChildItems:children];
return item;
}
#end
You can subclass TLTreeTableViewController instead of TLTableViewController if you want collapsable levels. Let me know if you need more help.
EDIT
Sorry, I missed the part that says you want to display it all at once. Basically, I think the easiest way to do this would be to basically have a recursive structure that gets the description of each object. This could be a string or even a UIView that you could then place inside your tableviewcell.
Lets stick with a dictionary for now. Each dictionary representation can have information about itself and its children. The template can be:
<LevelInfoDictionary>
<NSObject>someObjectThatRepresentsInfoAboutThisLevel
<NSArray>arrayOfInfoDictionariesThatRepresentChildren
</LevelInfoDictionary>
Then to implement your recursive method:
- (NSDictionary *)getLevelInfo
{
NSMutableArray *childInfo = [NSMutableArray array];
for(ClassName *child in self.children)
{
[childInfo addObject:[child getLevelInfo]];
}
return [NSDictionary dictionaryWithObjectsAndKeys:
<descriptionOfThisLevel>, #"CurrentLevel",
childInfo, #"children>, nil];
}
END EDIT
Basically, as some of these other guys have said, you should create your tableview that displays all of your top level objects. From there, after you select an object, you should be pushed to a new tableview that uses a different fetch request with the predicate like:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"parent = %#",
selectedParentObject];
Then you can use sort descriptors to sort the NSFetchRequest you are using.
Alternatively, you could just fetch the children by using the property on the parent object and store that in an array sorted by your sort descriptors.
One other thing that I should mention is that currently the sort descriptor does not accomplish anything. You may not notice this because there are other parts of the design that you should change, but since an NSDictionary does not have an order (it is a hash table), sorting objects before placing them in a dictionary does nothing.
From the ViewController for the TableView or CollectionView you should start by showing all of the top level objects (No parent). From there as a user selects an object that parent becomes the current level and the ViewController should refresh its data source to show all of the child elements at that level. You can then traverse back up to the parent via back button.
Let me know if you need any more detail.
Your loop doesn't make sense, because you want a list of the dictionaries for the structure of the children, but what you actually do it to overwrite it each time. You probably want something like:
NSMutableArray *subLevels = [NSMutableArray array];
for (MainLevel *childLevel in children)
{
[subLevels addObject:[self getStructureOfLawForLevel:childLevel]];
}
[mutableDict setObject:subLevels forKey:#"sublevels"];
I guess you want to show each level in a table view and drill to another table view for each subsequent level. That should be simple based on the dictionary which gives you a name to display and an optional array which defines whether drilling is possible and the data to pass to the next view controller.
In case it helps someone else, here's what I ended up doing...
- (NSArray *)getStructureAsArrayForLevel:(MainLevel *)child
{
NSMutableArray *thisChildAndChildren = [[NSMutableArray alloc]initWithCapacity:2];
[thisChildAndChildren addObject:child];
if (child.childLevels)
{
// children exist
// sort the children into order
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"order" ascending:YES];
NSArray *sortdescriptors = #[sortDescriptor];
NSArray *children = [child.childLevels sortedArrayUsingDescriptors:sortdescriptors];
// get an array for each child via recursion
for (MainLevel *child in children)
{
[thisChildAndChildren addObject:[self getStructureAsArrayForLevel:child]];
}
}
return [thisChildAndChildren copy];
}
Then I am using similar recursive function to convert the array to NSAttributedString and display in textView.
I really DO NOT like recursion. I don't know why but I find it SOOOOOOO hard to get my head around the logic, and when it's done it seems so obvious... Go figure!
Thanks to everyone for suggestions, help etc...
EDIT
It isn't exactly right, as the first layer has a slightly different structure to those that follow. at some point I need to change this to have the top level as a directory and use the very first level as the key for the directory, then add the complete array as the object for that key. But it works... my brain aches... and I can live with it until I get around to changing it.
Is it possible to sort a UITableView section alphabetically?
I have an array of objects and in section 0 I have only one item from the array in this section. It could be any item in the array that I swap. section 1 will have the remainder of the objects in the array on display.
e.g.
A B C D
C A B D -- I want C to now be at section 0, the rest in alphabetical order in section 1
D A B C -- I want D to now be at section 0, the rest in alphabetical order in section 1
I've tried moving objects around, sorting and then moving but with no luck. Any suggestions would be great before I need to change it to a UIViewController with a Label and a UITableView below this label to simulate this.
You can sort the Object Array Alphabetically using sortUsingSelector (The array does need to be of type NSMutableArray rather than just a NSArray) and adding a method which returns an NSComparisonSort value in your Model Class. MyObject being the class of your Custom Object in this case. The sort is being done based on a letter property which is a NSString but could be something else.
// Just before wherever your Data Source is returned in your MyObject Class
[myArray sortUsingSelector:#selector(compareTo:)];
// Method which does the Comparison, also in your MyObject
- (NSComparisonResult)compareTo:(MyObject *)otherObject {
return [self.letter caseInsensitiveCompare:otherObject.letter];
}
Now to put the one object at Index 0, you could do the following. I assume you know the index of the object you want to take out...
MyObject *temp = [[myArray objectAtIndex:index] retain];
[myArray removeObject:temp];
[myArray insertObject:temp atIndex:0];
This preparation would be done before the Table View Delegate Methods are called. Possibly in your Table View Init or ViewWillLoad/ViewDidLoad
Update: Here is how to achieve the same Alphabetical sort using a SortDescriptor:
// This is setting up the Sort Descriptor for comparing the two objects based on the property 'letter'
NSSortDescriptor *sorter = [[[NSSortDescriptor alloc] initWithKey:#"letter" ascending:YES] autorelease];
NSArray *sortDescriptors = [NSArray arrayWithObject:sorter];
// This is the sorted array returned based on the property 'letter'
NSArray *sorted = [myArray sortedArrayUsingDescriptors:sortDescriptors];