Reputation: 1072
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
Reputation: 3167
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
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
As a third alternative, you could instead implement a category on NSObject and use objc_setAssociatedObject to attach data to the object. Example:
#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
#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