Rolling your own iPhone treeview control: Summary, conclusion, and download links

It was a long drawn series, but I'm happy that we're finally done with "Rolling your own iPhone treeview control". To summarize the posts:
MyTreeViewPrototype

Apologies to those who had to wait a long time since this series was started last March 8th -- it was hard to juggle writing about this control *and* writing the app that I was using it in. I hope the wait was worth it.

For those who wish to download the code for this series you can get it here:

MyTreeViewPrototype on GitHub

If you have any questions about the tree view control, or improvements you wish to apply to it, feel free to contact me by commenting on any of the posts in this blog.

About Jon Limjap

Jon Limjap has been programming since he was 12 and hasn't stopped yet. He was gone for a while in iOS and Java land, but is now back in .NET searching for unicorns and hunting down dragons.
This entry was posted in Technical Articles and tagged , , . Bookmark the permalink.

15 Responses to Rolling your own iPhone treeview control: Summary, conclusion, and download links

  1. Thomas says:

    Thank you for this great tutorial, that’s exactly what I have been looking for! IMHO you can improve the UX a lot by calling tableview’s deleteRowsAtIndexPaths or insertRowsAtIndexPaths instead of good ol’ reloadData.
    #pragma mark -
    #pragma mark Table view delegate

    - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    MyTreeNode *node = [[treeNode flattenElements] objectAtIndex:indexPath.row + 1];
    if (!node.hasChildren) return;

    NSUInteger descendantCount;

    if (node.inclusive) {
    descendantCount = [node descendantCount];
    node.inclusive = false;
    } else {
    node.inclusive = true;
    descendantCount = [node descendantCount];
    }

    [treeNode flattenElementsWithCacheRefresh:YES];

    NSMutableArray *updateArray = [[NSMutableArray alloc] init];
    for (int i = 0 ; i < descendantCount; i++) {
    [updateArray addObject:[NSIndexPath indexPathForRow:indexPath.row + 1 + i inSection:indexPath.section]];
    }

    if (!node.inclusive) {
    [tableView deleteRowsAtIndexPaths:updateArray withRowAnimation:UITableViewRowAnimationNone];
    } else {
    [tableView insertRowsAtIndexPaths:updateArray withRowAnimation:UITableViewRowAnimationNone];
    }
    [updateArray release];
    [tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationNone];
    }

    Cheers, Thomas

  2. Jon Limjap says:

    Awesome suggestion Thomas! That’s definitely something to look into.

    I’ll check your code when I get the time (too little lately, sadly).

  3. Giorgio says:

    Great tutorial! Thank you! Only a question: how should I do if I wanted to distinguish between tap on the cell row and tap on the circle round image?

    Giorgio

  4. Jon Limjap says:

    @Giorgio,

    In this example I didn’t make the circle tap image a tappable object. If you want to do something like that you will have to modify the custom UITableViewCell and instead of using an UIImageView for the circle round image, use a UIButton instead with the same image and no border. Then somehow connect that button with an event, emit the cell’s indexpath, and then handle the rest. At least that’s the general idea. :)

  5. sakrist says:

    Thanks!
    you save my time :)

  6. sakrist says:

    @Thomas, thanks! :) Really good looking!

  7. Giorgio says:

    Hi Jon. I have many leaks when I try to release the treeNode. Is it possible that the flattenElementsWithCacheRefresh method create some leaks?

  8. Jon Limjap says:

    @Giorgio,

    Please try to use Thomas’s code above if that fixes the problem. Thanks :)

  9. Josh Pressnell says:

    Great work! I still need to memory-profile the code with Thomas’ updates, but the view looks very nice so far.

    I’ve made a few updates to better improve the user experience, on top of Thomas’ update. I’ll be adding an update to have the arrows be buttons so the table cells themselves are selectable and to allow re-ordering of the tree-nodes within the table shortly. I’ll post code here.. all I ask is kind attribution in the source files if you merge my updates later. ;)

    We should be able to update the tree node’s graphic without reloading the cell. Reloading the cell causes a “blip” in the indentation when it updates so it jumps from lower in the tree to higher as it reloads.

    Add the following method to MyTreeViewCell.h/.m:

    - (void)updateArrow {
    [self.arrowImage setImage: [UIImage imageNamed:self.expanded ? @"CircleArrowDown_sml" : @"CircleArrowRight_sml"]];
    [self.arrowImage setNeedsLayout];
    [self.arrowImage setNeedsDisplay];
    }

    Then add/update the following in the RootViewController.m (note the change in the animations in the reloadCells calls as well as the use of updateArrow):

    NSMutableArray *updateArray = [[NSMutableArray alloc] init];
    for (int i = 0 ; i < descendantCount; i++) {
    [updateArray addObject:[NSIndexPath indexPathForRow:indexPath.row + 1 + i inSection:indexPath.section]];
    }
    if (!node.inclusive) {
    [tableView deleteRowsAtIndexPaths:updateArray withRowAnimation:UITableViewRowAnimationTop];
    } else {
    [tableView insertRowsAtIndexPaths:updateArray withRowAnimation:UITableViewRowAnimationBottom];
    }
    [updateArray release];
    MyTreeViewCell* cell = (MyTreeViewCell*)[tableView cellForRowAtIndexPath:indexPath];
    cell.expanded = node.inclusive;
    if( cell )
    [cell updateArrow];

  10. Josh Pressnell says:

    I have a little more profiling to do, but I found at least the following memory leak:

    In MyTreeView.h, the following are retained properties:

    @property (nonatomic, retain) UILabel *valueLabel;
    @property (nonatomic, retain) UIImage *arrowImage;

    But in MyTreeView.m, you use the accessor to assign non-released objects to them:

    self.valueLabel =
    [self newLabelWithPrimaryColor:[UIColor blackColor]
    selectedColor:[UIColor whiteColor]
    fontSize:20.0 bold:YES];
    self.valueLabel.textAlignment = UITextAlignmentLeft;
    [content addSubview:self.valueLabel];

    self.arrowImage =
    [[UIImageView alloc] initWithImage:
    [UIImage imageNamed:self.expanded ?
    @"CircleArrowDown_sml" : @"CircleArrowRight_sml"]];
    [content addSubview:self.arrowImage];

    What you really want is this:

    self.valueLabel =
    [[self newLabelWithPrimaryColor:[UIColor blackColor]
    selectedColor:[UIColor whiteColor]
    fontSize:20.0 bold:YES]autorelease];
    self.valueLabel.textAlignment = UITextAlignmentLeft;
    [content addSubview:self.valueLabel];

    self.arrowImage =
    [[[UIImageView alloc] initWithImage:
    [UIImage imageNamed:self.expanded ?
    @"CircleArrowDown_sml" : @"CircleArrowRight_sml"]]autorelease];
    [content addSubview:self.arrowBImage];

  11. bimal says:

    hi ,
    i try to create tree view and its works fine i read your code and its looks cool… but my main concern here is i want checkable tree view and i add one button in custom cell so i am little beat confuse how to store the status of data is it checkable or not. if you have any solution please help me for it

    thanks in advance
    bimal

  12. Jon Limjap says:

    Hi bimal,

    I think that should be a simple matter of putting a checked/unchecked boolean in the MyTreeNode class and representing that with a custom checkbox control (or a UISwitch). You’ll need to set up the proper events so that checking a parent would propagate to the children properly. I’m not sure what the ramifications are to the tree itself but I think it’s doable.

  13. coolflame says:

    Hi Jon,

    I tried addChild at runtime by use the following code.
    MyTreeNode *node1 = [[MyTreeNode alloc] initWithValue:@”Node1″];
    [treeNode addChild:node1];
    [TableView reloadData];
    It works fine first time but cause error second time at
    MyTreeNode *node = [[treeNode flattenElements] objectAtIndex:indexPath.row + 1];
    Please help me solve it.

    Thanks
    coolflame

  14. Duncan Groenewald says:

    Thanks, this has helped get a basic idea of things. Would be nice if you could show us a couple of additional tricks, like:
    1. Click on image to expand/collapse and select row
    2. Click on text to select row only – don’t expand/collapse
    3. Click on text to segue to a new tree view with the selected row as the root level and with a back toolbar button to return to the previous root view (not just up one level)

    Not sure I have been able to figure out the logic of what happens when clicking on a row. How would I be able to detect the Expand/Collapse action to set a field on the object ? Seems hitTest can be used to check if the hit was on the image but how then allow expanding the row ?

  15. Hi Jon, thank you so much for this! I was looking all over the web for a solution like this but couldn’t find any until yours. I know you probably have very little time, but could you give a few pointers on how to implement reordering of the rows? I tried with standard methods, but that disabled the expanding/collapsing and removed the cell labels.

    Again, thank you so much making this tutorial!

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>