RJR
RJR

Reputation: 1072

DataModel for ios TreeView Component

Rolled out a Tree View Component in ios to represent hiererchical data. Tree is expandable by tapping the (+) in in each node and collapsable ny tapping the (-)node.

My classes looks like this,

//Tree item

@interface Node : NSObject
@property (nonatomic, strong) NSString *nodeCaption;

- (void)addNode:(Node *)node;
@end

//Tree Model Class

@interface Tree : NSObject

- (id)initWithParentNode:(Node *)node;

//Tree View Class

@interface TreeView : UIView

- (id)initWithTree:(Tree *)tree andFrame:(CGRect)frame;

- (void)reloadData;

@end

//How to use it

@implementation

 Node *rootNode = [[Node alloc] init];
 rootNode.caption = @"root";


 NSArray *fruits1 = [NSArray arrayWithObjects:@"Apple", @"Peach", nil];
 Node *fruit1Node = [[Node alloc] init];
 fruit1Node.caption = @"set1";

 for (NSString *fruit in fruits1) {
    Node *aNode = [[Node alloc] init];
    aNode.caption = fruit;
    [fruit1Node addNode:aNode];
 }

 NSArray *fruits2 = [NSArray arrayWithObjects:@"Orange", @"Mango", nil];
 Node *fruit2Node = [[Node alloc] init];
 fruit2Node.caption = @"set2";

 for (NSString *fruit in fruits2) {
    Node *aNode = [[Node alloc] init];
    aNode.caption = fruit;
    [fruit2Node addNode:aNode];
 }

 [rootNode addNode:fruit1Node];
 [rootNode addNode:fruit2Node];

 Tree *tree = [[Tree alloc] initWithParentNode:rootNode];

 TreeView *treeView = [[TreeView alloc] initWithTree:tree andFrame:self.frame];
 [self.view addSubview:treeView];
 [treeView reloadData];

 @end

This will output something similar to below

-Root
   -set1
     Apple
     Peach
   -set2
    Orange
    Mango

This is perfectly ok in this case. But the problem is with the code we use to create the input for the tree, the place where we loop through the data model we hold and covert it into a model that tree component can work with. In my case, all my tree nodes are 'Category' objects and I'm forced to covert all my category objects to Node objects to make use of tree. So I would like to know is there a way we can use any datamodel the user of the tree intends to use rather than forcing.

Also I wouldn't like to ask the users to subclass my Abstract class. Because, they may be already have their own parent.

Upvotes: 1

Views: 788

Answers (1)

josh-fuggle
josh-fuggle

Reputation: 3167

Using Protocols

If I understand you correctly, you would like to allow the client to use their own custom objects as "Node" objects.

If this is correct, then you could have your "Node" as an protocol instead class, for example:

// If you want to add your objects to a tree, then you must implement this protocol...
@protocol NodeProtocol <NSObject>

@property (nonatomic, strong, readonly) NSMutableArray *children;

-(void)addNode:(id <NodeProtocol>)node; // Node items must support adding children nodes
-(NSString *)description; // Node items need to be able to print a textual description of themselves

@end

// Custom class implements the protocol
@interface CustomNode : NSObject <NodeProtocol>
...
@end

Using Encapsulated Objects

Alternatively, you could encapsulate abstract objects inside the Node object, for example:

@interface Node : NSObject
@property (nonatomic, strong) NSObject *nodeItem;
-(void)addNode:(Node *)node;
@end

Using Categories

As a third alternative, you could instead implement a category on NSObject and use objc_setAssociatedObject to attach data to the object. Example:

Category

#import "NSObject+NodeCategory.h"

#import <objc/runtime.h>

static char const *kNodeKey = "NodeKey";

@implementation NSObject (NodeCategory)

-(void)addNode:(NSObject *)node
{
    NSMutableArray *children = [self children];
    if (!children) children = [NSMutableArray new];
    [children addObject:node];
    [self setChildren:children];
}

-(void)setChildren:(NSMutableArray *)children
{
    objc_setAssociatedObject(self, kNodeKey, children, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

-(NSMutableArray *)children
{
    return objc_getAssociatedObject(self, kNodeKey);
}

@end

Example Usage

#import <Foundation/Foundation.h>

#import "NSObject+NodeCategory.h"

int main(int argc, const char * argv[])
{

    @autoreleasepool {

        NSObject *node = [NSObject new];
        node.children = [NSMutableArray arrayWithArray:@[@"Kiwi Fruit", @"Apple", @"Bannana"]];
        [node addNode:@"Dragon Fruit"];
        NSLog(node.children.description);

    }
    return 0;
}

Upvotes: 5

Related Questions