简体   繁体   中英

CoreData, recursive structure… I think

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM