Q: how to display hierarchical data from xml or json
jolo2000 opened this issue · 14 comments
Hello,
i would like to display some hierarchical data from a xml-file or json-response.
Now I am looking for some hints or tips how to start with.
Yours Johannes
Hi Johannes,
XML and JSON formatted data are a good fit for this control because they are both single parent hierarchies of data. I do not know how far along the path of development you are so I'll start from the beginning. Feel free to skip to the part you need. :-)
To display the data, we need to get it from somewhere, parse it into a our data model, and connect our data model to the custom view. This can range from the simple to the very complex. The steps are summarised below.
High Level Overview:
- Load - Request / Download / Open the data
- Parse - Parse the xml or json into your data model
- Display - Connect the data model to the custom view
Here is an example of a the pieces we can put together to perform each task listed above. This is a simple structure. We can use different pieces to get different behaviour.
Simple Structure:
- Load - Use a local JSON file. Load the contents into an NSData object using "NSData dataWithContentsOfFile:"
- Parse - Use one of the many quality JSON parsing libraries. I enjoy using TouchJSON. https://github.com/TouchCode/TouchJSON
- Display - Implement the PSTreeGraphModelNode protocol in your data model objects. Then, supply the root object of your data model to the tree graph view. The tree graph view's delegate needs some simple code to configure the display nodes when asked. Basicly, it will give you a single node and a single model object so you can use the model object to configure the properties of the node.
Let's get started. Imagine we had a JSON file named jojannes.json. Let's say that it has been added to our project so it gets placed into our application bundle during the build. This imaginary project already includes TouchJSON and the PSTreeGraph code we need. Here is some code to get us through the first two steps. Best practice would be to put this code in view controller that contains the custom view.
#import "CJSONDeserializer.h"
// add the following method somewhere in your @implementation
-(void) loadJSONData
{
// Find the file
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"jojannes" ofType:@"json"];
if (filePath) {
// Load data in the file
NSData *theJSONData = [NSData dataWithContentsOfFile:filePath];
if (theJSONData) {
// Parse the file
NSError *theError = nil;
NSDictionary *theObject = [[CJSONDeserializer deserializer] deserializeAsDictionary:theJSONData
error:&theError];
if (theObject) {
// File loaded and parsed. Insert code to populate the data model here.
} else {
NSLog(@"Could not parse JSON file.");
// TODO: Use the NSError object to build better error logging
}
} else {
NSLog(@"Could not load NSData from file.");
}
} else {
NSLog(@"Please include jojannes.json in the project resources.");
}
}
The Data Model
Your data model can be structured however you like it. The only requirement is that the objects support the PSTreeGraphModelNode protocol. See the ObjCClassWrapper for an example. Options include: a set of custom classes, core data objects, or even something like a category on NSDictionary so you can use the result of TouchJSON directly.
I would include an example here but this is the part that actually requires some work. It is also easy to just gloss over in a tutorial. In my defence, once you start using this for something specific, you will want your own data model anyway for other things unrelated to the presentation of the data.
Customising the Display
The example project includes a ObjCClassTreeNodeView.xib to define the presentation of a node. You may want to play with it at some point so it displays your data the way you like it. The xib file in the sample code is backed by a sub class of PSBaseLeafView called PSHLeafView. Take a look at the two classes and compare them. You only need to do the bare minimum to build your own subclass of PSBaseLeafView. Note: don't forget to change the properties in your node xib to use your "custom class".
Configuration
Now to set up our custom view and to tell it about the root node in our data model. Here are the basic steps. Just about everything goes into your view controller class because it is supposed to be the bridge between your model and the presentation. That is exactly what we want to do here. The PSHTreeGraphViewController class provides a good example of the following steps.
View Configuration Overview:
- Configure the custom view's delegate. (Just like you would for a tableview)
- Tell it which xib file to use for displaying nodes
- Set the root model node
First step is to implement the delegate method and set the delegate property for the custom view. At this time, there is only one delegate method to implement. It is called each time the graph needs to configure a new view to display a data object in your model. It provides you with the new view (this will be your custom subclass of PSBaseLeafView) and one of your model nodes to configure it with. The example configures the delegate property in the viewDidLoad method.
// Set the delegate to self.
[self.treeGraphView setDelegate:self ];
Next we set the nib to use for each node view.
// Specify a .nib file for the TreeGraph to load each time it needs to create a new node view.
[self.treeGraphView setNodeViewNibName:@"YourCustomClassNameHere" ];
Configure the ModelRoot property. You can use "." notation or the explicit method, "setModelRoot:". The example just wraps this method in a helper called "setRootClassName:".
// Set object as data model rood as the TreeGraph's root.
[treeGraphView setModelRoot:YourRootObjectHere ];
Finally, ...
That's it! It seems like a bit of work at the start but you will find this pattern repeats itself all over UIKit. Everything is loosely coupled and you can hack away on other parts of your project and forget about it.
And one more thing...
To keep things looking good during resizes, rotations, etc implement the following optional method in your view controller. If you already have one, be sure to add a single call to parentClipViewDidResize:
- (void) willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
duration:(NSTimeInterval)duration
{
// Keep the view in sync
[self.treeGraphView parentClipViewDidResize:nil];
}
Warm Regards,
Ed
Hi Ed,
thank you so much for this extensive explanation. I got the main idea and the missing links. Probably I will start with reading in a plist with hierarchical data. If this is successful, then I will try the JSON part.
Yours Johannes
Hi,
this is just an info.
I added this method in my view controller:
- (void) willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
duration:(NSTimeInterval)duration
{
// Keep the view in sync
[self.treeGraphView parentClipViewDidResize:nil];
}
As a result, the orientation change of the uiviews didn't work anymore. BTW. i am using a tabbar application.
Removing the code from the view controller class, solved the issue.
HTH Johannes
Does anyone have an example of loading from JSON or Coredata?
Simple structure for loading the model above. :-)
Very good resource! Can PSTreeGraph be integrated with an iOS 7 ( storyboard ) project? thanks
Hi,
Here is my json content:
{
“name": “Example”,
"ID": “01234”,
"accounts": [
{
"ID": “0”,
"name": "Example"
}
],
"children": [
{
“name”: "Example1”,
"ID": “56789”,
"accounts": [
{
"ID": “1”,
"name": "Example1"
}
],
"children": []
}
]
}
I have used the same ObjCClassWrapper file to load my Json data into the graph. I have modified the subclasses method like this:
- (NSArray *) subclasses
{
// If we haven't built our array of subclasses yet, do so.
if (subclassesCache == nil) {
return [NSArray array];
}
return subclassesCache;
}
and in PSHTreeGraphViewController.m file, I have modified the below method:
-(void) configureNodeView:(UIView *)nodeView
withModelNode:(id <PSTreeGraphModelNode> )modelNode
{
NSParameterAssert(nodeView != nil);
NSParameterAssert(modelNode != nil);
// NOT FLEXIBLE: treat it like a model node instead of the interface.
ObjCClassWrapper *objectWrapper = (ObjCClassWrapper *)modelNode;
objectWrapper.subclasses = self.JSONFromFile.allValues;
MyLeafView *leafView = (MyLeafView *)nodeView;
// button
if ( [objectWrapper childModelNodes].count == 0 ) {
[leafView.expandButton setHidden:YES];
}
// labels
leafView.titleLabel.text = objectWrapper.name;
//leafView.detailLabel.text = [NSString stringWithFormat:@"%zd bytes",
//objectWrapper.wrappedClassInstanceSize];
}
The application did not crash but it didn't load my content. Where am I doing it wrong?
I'll take a look when I get back to my desk.
Okay. Thank you.
Hi @epreston ,
First of all, I want to thank you , for all this work.
I've been trying to implement the PSTreeGraph with data from a Json File but I'm stuck.
May I provide you a Github repository with my example in order to help me? That would be awesome.
Thanks a lot in advance.
HI, can you give me a demo from json or plist
Hi,
Do you have an json example used in this application ?
Best Regards.