Become a MacRumors Supporter for $50/year with no ads, ability to filter front page stories, and private forums.
I looked into this before and found a sample on GitHub. I don't know if this is the same one or not, but it gives you the idea.

I didn't use it because I went with the "slide to show buttons" type cell instead.

I'm not sure what data stru it uses, but it should at least give you and idea of how it works.

https://github.com/Augustyniak/RATreeView
 
It's really easy, actually. You can set the 'estimatedRowHeight' property of your tableView =
UITableViewAutomaticDimension (make sure NOT to implement the 'estimatedHeightForRowAtIndexPath' or 'heightForRowAtIndexPath' methods).

You then use auto-layout on the UITableViewCell views. There should be constraints that essentially let the cell calculate how 'tall' it should be. For example, if you have a label in the middle of the cell that is always 20px high, you would have two constraints (top and bottom). Depending on the values of those two constraints, the table view cell can tell automatically how tall it should be.

You can then programmatically change the values of these constraints, which lets you animate the change in height, like this:

Code:
//if it used to be 20, and I change it to 50, the cell will increase in height

cell.topDistanceConstraint.constant = 50;

UIView.animateWithDuration(0.15) {
   cell.layoutIfNeeded();
}
        
self.tableView.beginUpdates();
self.tableView.endUpdates();
 
It's really easy, actually. You can set the 'estimatedRowHeight' property of your tableView =
UITableViewAutomaticDimension (make sure NOT to implement the 'estimatedHeightForRowAtIndexPath' or 'heightForRowAtIndexPath' methods).

You then use auto-layout on the UITableViewCell views. There should be constraints that essentially let the cell calculate how 'tall' it should be. For example, if you have a label in the middle of the cell that is always 20px high, you would have two constraints (top and bottom). Depending on the values of those two constraints, the table view cell can tell automatically how tall it should be.

You can then programmatically change the values of these constraints, which lets you animate the change in height, like this:

Code:
//if it used to be 20, and I change it to 50, the cell will increase in height

cell.topDistanceConstraint.constant = 50;

UIView.animateWithDuration(0.15) {
   cell.layoutIfNeeded();
}
      
self.tableView.beginUpdates();
self.tableView.endUpdates();


So my main problem wasn't dealing with the TableView constraints, but more of how do I get a tree structure into a tableView that expects a flat array since indexPath.row won't account for child comments.

Long story short, I solved problem by flattening my tree array and tracking what level the comment node is at. I then check what node level the comment is at within the "cellForRowAtIndex" and adjust the left constraint accordingly. Maybe there is an easier way, but this solved my problem without having to wedge a 3rd party framework into the app.
 
So my main problem wasn't dealing with the TableView constraints, but more of how do I get a tree structure into a tableView that expects a flat array since indexPath.row won't account for child comments.

Long story short, I solved problem by flattening my tree array and tracking what level the comment node is at. I then check what node level the comment is at within the "cellForRowAtIndex" and adjust the left constraint accordingly. Maybe there is an easier way, but this solved my problem without having to wedge a 3rd party framework into the app.
The tableView doesn't have to have a flat array. I used a grouped type in FRC from CoreData in mine for a 2 level tableView.

I'm not sure, but doesn't the tableView allow more levels when it's grouped?

I wouldn't mind seeing your solution, I wanted a treeview thing for browsing resources and some of the examples I've seen are overly complex.
 
The tableView doesn't have to have a flat array. I used a grouped type in FRC from CoreData in mine for a 2 level tableView.

I'm not sure, but doesn't the tableView allow more levels when it's grouped?

I wouldn't mind seeing your solution, I wanted a treeview thing for browsing resources and some of the examples I've seen are overly complex.

Sure, but what do you mean grouped? Splitting parts of the tableView by Section? I've done that before, but my problem was I wanted to see Parent + Child + Child's Child all in one section. Maybe there is a way to do this?
 
I still don't really fully understand your question. Are you trying to say there may be sub-table views inside some table view cells? If so, I would just have two types of UITableViewCell, one would be dynamically resizable and would contain a subview UITableView. The other type of cell would be the 'content' table view cell which gets shown. When being initialized, the 'cellForRowAtIndex...' would grab an object from the data array, and depending on whether it's an array or an object, I would return the table-view containing cell or the content cell. It would pass this array/data object to the UITableViewCell via a custom initializer so that it can decide how to display it.

Depending on whether a specific node has sub nodes, I would switch between one or the other, and they would have a property called 'collapsed' which indicates if they are collapsed or not. In order to do the actual collapsing, I would use auto layout like I mentioned in my previous post.
 
Sure, but what do you mean grouped? Splitting parts of the tableView by Section? I've done that before, but my problem was I wanted to see Parent + Child + Child's Child all in one section. Maybe there is a way to do this?

Here's the FRC, I used the "groupedBy" to group the results into seperate groups based on a field in CoreData.

Code:
    NSFetchedResultsController *myFRC = [Contact MR_fetchController:myFetchRequest
                                                              delegate:self
                                                          useFileCache:NO
                                                             groupedBy:@"category"
                                                             inContext:localContext];

This gives me a table where I can have all the sub categories listed. This would be the same as the top 2 levels of a discussion list where you have all the topic as categories with all the postings listed in their group. I use this for a contact selector, where you have all the contacts listed by type.

The upside of this is that management is automatic. CoreData manages add/edit/delete.

In this screenshot, science, sports and tech are from a field called 'category' just as above in the 'groupedBy'
BBC, AP are just records with the same category.

The table is managed automatically as records are added so there's really no work on your part.


upload_2016-10-5_1-23-37.png




This is the slide out for the hidden buttons that control the Add/Edit/Refresh (or whatever you would want them to do)

upload_2016-10-5_1-31-30.png




This is the right side of the slide out that is used for delete.

upload_2016-10-5_1-31-47.png




So all the updates from the datasource (both ways) are automatic. I don't have to worry about the category going to zero or adding more records, the FRC takes care of that. It can even be used for caching results and updating other parts of the app.

I didn't go further with the "drill down" because I needed the slide out hidden menus. The slide outs are from the same GitHub as the slide out for the left/right view so there's a drill down hidden slide out system.

Again, all the work to manage everything (add edit delete) is automatic because it's built in.

I made this a "blue print" so that when you double tap the top bar <where it says "filename" you it swaps out to another feed source automatically.

The code I linked in the above post was a different option and I didn't take the time to merge the features of it into the hidden slide out system.

I don't know offhand how FRC would handle categories of categories of categories... I image you could just grab each one as needed or when the user slides out, have that sub-topic come up in a new table.

I don't know your screen layout system, but what I did is a different kind of drill down. It allow the whole topic/category to take up as much of the screen as you want and you just slide out the picker menu on the left.

Even the view on the right is a table view of collection views. This give a separate scrollable categorized view of the information that comes from the left where you pick the sources.

You might think of this as a different way of seeing the same info, it's just hidden so that you get more screen size for the views.

I don't know if this is even close to what you had in mind or not, plenty of different ways of displaying the same info.

I personally like a large window of data with hidden controls for sources.

Let me know if you'd like more info on this or if it's not something of interest.
[doublepost=1475657527][/doublepost]Here's the GitHub for the controller if anyone is interested in it. It's the same person that did the cell part.

https://github.com/John-Lluch/SWRevealViewController

All I did was add the cell sliders to a groupedBy VC.

It would be cool for someone to make one that had multi level drill down with sliders built in, but I ran out of time for that.

[edit] I just had an idea to modify the slide out, you can make it so the slide out is the next VC, just create a VC on the fly each time the user slides it out.
 
Last edited:
I still don't really fully understand your question. Are you trying to say there may be sub-table views inside some table view cells? If so, I would just have two types of UITableViewCell, one would be dynamically resizable and would contain a subview UITableView. The other type of cell would be the 'content' table view cell which gets shown. When being initialized, the 'cellForRowAtIndex...' would grab an object from the data array, and depending on whether it's an array or an object, I would return the table-view containing cell or the content cell. It would pass this array/data object to the UITableViewCell via a custom initializer so that it can decide how to display it.

Depending on whether a specific node has sub nodes, I would switch between one or the other, and they would have a property called 'collapsed' which indicates if they are collapsed or not. In order to do the actual collapsing, I would use auto layout like I mentioned in my previous post.

So here is a screenshot of what my initial question was trying to achieve.

I wanted to know how they created the effect of having a top level comment (e.g. someone replying directly to the thread) and having all replies shift to the left (and reply to the replies.....etc...). This can potentially happen an infinite amount of times for any comment, so I can't hard code a limit on how deep the replies can go.

Example. An array contains 5 direct thread replies. The 5 top level replies also hold replies to them as well. These are all the same type (A class I named CommentModel). Each CommentModel has a property called childComments which holds the next level of comments below it etc... "childComments = nil" if there are no replies to that comment and the node stops.

0 = Top level reply directly to thread
1 = Reply to top level
2 = Reply to 1 (first reply)
3 = Reply to 2 etc....

0 -> 1 -> 1 (has to two replies directly to the top level comment node)
0 -> 1 -> 2 (has a one reply to the top level comment node and one reply to the reply node..I know it's getting confusing with redundant words)
0 -> 1 -> 2 -> 2 (one reply to top level comment node and two replies to the reply (1) node)
0 (top level comment with no replies)
0 (top level comment with no replies)

Total number of rows to view all comments: 12

So if I use the expected way cellForRowAtIndexPath, I will only get 5 rows, since the initial array has hold 5 top level comments, but each comment could have associated replies.

So, my solution (I can post code if you're curious) was using a combination of recursion and node tracking

1. Flatten array so all nested comments are moved into a flat array.
2. While I parse this, I set a property called nodeLevel which specifies how deep the reply is.
3. Pass that array to the tableView which now has the correct count to view ALL comment and replies for a particular thread.
4. I added an AutoLayout constraint out for the left margin of the cell and set it's constant = (nodeLevel * 10) which will then indent to the appropriate distance based on what level the reply is.

There may be a simpler approach or UITableView CAN deal with a tree structure, I'm just not aware of how to do it...
 

Attachments

  • IMG_1930.PNG
    IMG_1930.PNG
    125.1 KB · Views: 109
Last edited:
So here is a screenshot of what my initial question was trying to achieve.

I wanted to know how they created the effect of having a top level comment (e.g. someone replying directly to the thread) and having all replies shift to the left (and reply to the replies.....etc...). This can potentially happen an infinite amount of times for any comment, so I can't hard code a limit on how deep the replies can go.

Example. An array contains 5 direct thread replies. The 5 top level replies also hold replies to them as well. These are all the same type (A class I named CommentModel). Each CommentModel has a property called childComments which holds the next level of comments below it etc... "childComments = nil" if there are no replies to that comment and the node stops.

0 = Top level reply directly to thread
1 = Reply to top level
2 = Reply to 1 (first reply)
3 = Reply to 2 etc....

0 -> 1 -> 1 (has to two replies directly to the top level comment node)
0 -> 1 -> 2 (has a one reply to the top level comment node and one reply to the reply node..I know it's getting confusing with redundant words)
0 -> 1 -> 2 -> 2 (one reply to top level comment node and two replies to the reply (1) node)
0 (top level comment with no replies)
0 (top level comment with no replies)

Total number of rows to view all comments: 12

So if I use the expected way cellForRowAtIndexPath, I will only get 5 rows, since the initial array has hold 5 top level comments, but each comment could have associated replies.

So, my solution (I can post code if you're curious) was using a combination of recursion and node tracking

1. Flatten array so all nested comments are moved into a flat array.
2. While I parse this, I set a property called nodeLevel which specifies how deep the reply is.
3. Pass that array to the tableView which now has the correct count to view ALL comment and replies for a particular thread.
4. I added an AutoLayout constraint out for the left margin of the cell and set it's constant = (nodeLevel * 10) which will then indent to the appropriate distance based on what level the reply is.

There may be a simpler approach or UITableView CAN deal with a tree structure, I'm just not aware of how to do it...
How are you getting your data and how are you managing it?
It sounds like you're parsing it from an RSS feed or something and loading it into an array. In that case, the RATreeView should work.

I didn't dig deep into the RATreeView to see how it was doing what it did, but I think you should be able to run their sample code and just replace it with your data feed. The rest would be cosmetics and whatnot.

If you want a leaner solution, you could just dig out the logic of RATreeView and see how they do what they do.

From the sounds of it, a CoreData FRC would be overkill with the Add/Edit/Delete part, but would have the upside of data caching.
 
How are you getting your data and how are you managing it?
It sounds like you're parsing it from an RSS feed or something and loading it into an array. In that case, the RATreeView should work.

I didn't dig deep into the RATreeView to see how it was doing what it did, but I think you should be able to run their sample code and just replace it with your data feed. The rest would be cosmetics and whatnot.

If you want a leaner solution, you could just dig out the logic of RATreeView and see how they do what they do.

From the sounds of it, a CoreData FRC would be overkill with the Add/Edit/Delete part, but would have the upside of data caching.

The data is getting returned from REST response with JSON data. I have a PostModel type that has a "comments" property, which holds the comments for a specific post. This isn't populated until the user will select a post to view.

I looked into RATreeView but I felt it was overkill for what I wanted to do. A lot of times I feel that trying to wrap my head around 3rd Party Frameworks and getting them to work in my project, which may have slightly different use case to be more work than just doing it myself. On top of that, I typically like to know how to do things myself before using a framework that has lots of bells and whistles.

My code is actually pretty light weight. It's maybe < 25 lines of code?

Yea, I don't need the "Add/Edit/Delete" part since this is just displaying comments that were returned from the URLRequest.
 
The data is getting returned from REST response with JSON data. I have a PostModel type that has a "comments" property, which holds the comments for a specific post. This isn't populated until the user will select a post to view.

I looked into RATreeView but I felt it was overkill for what I wanted to do. A lot of times I feel that trying to wrap my head around 3rd Party Frameworks and getting them to work in my project, which may have slightly different use case to be more work than just doing it myself. On top of that, I typically like to know how to do things myself before using a framework that has lots of bells and whistles.

My code is actually pretty light weight. It's maybe < 25 lines of code?

Yea, I don't need the "Add/Edit/Delete" part since this is just displaying comments that were returned from the URLRequest.
LOL, that's exactly how I feel :D
I dig into their code and try to figure out "what do I need from this" to make it work with mine.

The things I showed in the pics above were from a 3rd party and I had to dig into it in order to make it work with iOS6 and even to make it work other 3rd party tools.

It can be a real pain. The RSS feed parser was a LOT of time to dig into and when I was done, I found that it didn't grab the pics out of the data. He left that for others to do, but nobody did it and posted it. So I have to find another tool if I want that.

I'm 100% with you on the 3rd party stuff, it can be a trap. However, there's some good code gems if you're willing to dig deep enough.
 
LOL, that's exactly how I feel :D
I dig into their code and try to figure out "what do I need from this" to make it work with mine.

The things I showed in the pics above were from a 3rd party and I had to dig into it in order to make it work with iOS6 and even to make it work other 3rd party tools.

It can be a real pain. The RSS feed parser was a LOT of time to dig into and when I was done, I found that it didn't grab the pics out of the data. He left that for others to do, but nobody did it and posted it. So I have to find another tool if I want that.

I'm 100% with you on the 3rd party stuff, it can be a trap. However, there's some good code gems if you're willing to dig deep enough.
Yea, if they provide documentation and specific examples on how it works then 3rd Party Frameworks are great. Most of the time though, they simply have an example program and expect you to figure out how to integrate it with with your app.
 
Yea, if they provide documentation and specific examples on how it works then 3rd Party Frameworks are great. Most of the time though, they simply have an example program and expect you to figure out how to integrate it with with your app.
That's exactly what I had to do. Even the two slide outs didn't work with each other.

On that pic I posted, there are scrolling RSS data feeds that are shown on the right.

I downloaded several 3rd party tools to do scrolling text. Had a BAD time trying to make them work, they had so much stuff like spacing calculators, speed calculators, etc..
I couldn't even get it to scroll right unless the length of the string was greater than the length of the field, so I padded the string with spaces just to get it to work right.

This is why when programmers interview for jobs, they want to see the code, it's a PITA to try and figure out what someone else's trying to do, much less try to debug or change behavior.
 
That's exactly what I had to do. Even the two slide outs didn't work with each other.

On that pic I posted, there are scrolling RSS data feeds that are shown on the right.

I downloaded several 3rd party tools to do scrolling text. Had a BAD time trying to make them work, they had so much stuff like spacing calculators, speed calculators, etc..
I couldn't even get it to scroll right unless the length of the string was greater than the length of the field, so I padded the string with spaces just to get it to work right.

This is why when programmers interview for jobs, they want to see the code, it's a PITA to try and figure out what someone else's trying to do, much less try to debug or change behavior.

If you're curious what my solution looks like check it. It's not the prettiest yet, but works. Next is to make them collapsible.
 

Attachments

  • Simulator Screen Shot Oct 5, 2016, 4.18.27 PM.png
    Simulator Screen Shot Oct 5, 2016, 4.18.27 PM.png
    132 KB · Views: 115
If you're curious what my solution looks like check it. It's not the prettiest yet, but works. Next is to make them collapsible.
That looks great. So it's a tree structure and when a cell is requested, you look at the prior cell to find the location and see if the next cell is a part of the same branch, otherwise you walk back up the array until you find out what should be the next cell.

I'd like to see the code for that logic, if you don't mind. I can imagine a simple limited depth one, but you don't know how deep it goes (unless you set limits).

In other words, you could use a for..next inside of a for..next inside a for..next, but that's going to have limits.

You'll have to use recursion to do it right.

One option would be to use a mutable array and keep track where the header is. Maybe a count of how many are showing under the current header and then when it's collapsed, remove that many cells from the display.

I guess all the information you'd need would be in the count of each branch, but the mutable array could keep a code for what type that element is, either a text or another branch.

Does your array just store arrays or texts mixed? If so, just check for type would work, otherwise, count alone should do the trick.

Either way, it looks like a pretty useful code snippet to have.
 
Sure, here is pretty much the logic I'm using to create the flatted tree of comments. Note this isn't perfect and each CommentModel all children as I'm not currently removing that at the moment.

Code:
var flattentedComments = [CommentModel]()
  
    func parseAndFlattenComments(comments : [CommentModel]) -> [CommentModel] {
        //Has top all top level Comments, so we need to iterate through each one to find their children.
        for parentNode in comments {
            self.flattentedComments.append(parentNode)
            self.parseNode(commentNode: parentNode)
        }
        return self.flattentedComments
    }
  
    func parseNode(commentNode : CommentModel) {
        if commentNode.childComments != nil {
            for comment in commentNode.childComments! {
                comment.nodeLevel = commentNode.nodeLevel! + 1
                self.flattentedComments.append(comment)
                if comment.childComments != nil {
                    self.parseNode(commentNode: comment)
                }
            }
        }
    }

Let me know if you have any questions on how it works. Long story short, I'm removing the treed comments and pushing them into a single leveled array. But I have a property called nodeLevel that keeps track at what position they should be at so they can be indented properly according to their response level.
 
Last edited:
Sure, here is pretty much the logic I'm using to create the flatted tree of comments. Note this isn't perfect and each CommentModel all children as I'm not currently removing that at the moment.

Code:
var flattentedComments = [CommentModel]()
 
    func parseAndFlattenComments(comments : [CommentModel]) -> [CommentModel] {
        //Has top all top level Comments, so we need to iterate through each one to find their children.
        for parentNode in comments {
            self.flattentedComments.append(parentNode)
            self.parseNode(commentNode: parentNode)
        }
        return self.flattentedComments
    }
 
    func parseNode(commentNode : CommentModel) {
        if commentNode.childComments != nil {
            for comment in commentNode.childComments! {
                comment.nodeLevel = commentNode.nodeLevel! + 1
                self.flattentedComments.append(comment)
                if comment.childComments != nil {
                    self.parseNode(commentNode: comment)
                }
            }
        }
    }

Let me know if you have any questions on how it works. Long story short, I'm removing the treed comments and pushing them into a single leveled array. But I have a property called nodeLevel that keeps track at what position they should be at so they can be indented properly according to their response level.
Thanks, I'll dig into it a bit later.
BTW, what parser are you using to grab your feeds?

I used item 2.4 from this site:
http://codewithchris.com/make-a-iphone-app-for-your-wordpress-site/

It wasn't complete in that it doesn't grab pics. I haven't found a replacement, maybe yours is better. That tutorial in that link was pretty good otherwise.

I'm pretty sure the code is on GitHub, but if you read it, it had a "to do" part in the description where he talks about parsing out the rest of the stuff.

I think Apple had a great one in some sample that included caching for off-line reading, but I haven't dug into that code yet. So much to learn...

[edit]
https://github.com/mwaterfall/MWFeedParser
Here's a direct link, it has > 2K stars and dates back to 2010, but still has some missing functionality. I wonder if everyone went in another direction.
 
Thanks, I'll dig into it a bit later.
BTW, what parser are you using to grab your feeds?

I used item 2.4 from this site:
http://codewithchris.com/make-a-iphone-app-for-your-wordpress-site/

It wasn't complete in that it doesn't grab pics. I haven't found a replacement, maybe yours is better. That tutorial in that link was pretty good otherwise.

I'm pretty sure the code is on GitHub, but if you read it, it had a "to do" part in the description where he talks about parsing out the rest of the stuff.

I think Apple had a great one in some sample that included caching for off-line reading, but I haven't dug into that code yet. So much to learn...

[edit]
https://github.com/mwaterfall/MWFeedParser
Here's a direct link, it has > 2K stars and dates back to 2010, but still has some missing functionality. I wonder if everyone went in another direction.

This isn't an RSS feed, just a JSON response from a REST call. BUT I have written my own RSS Feed viewer in the past. Again I just used Apple's NSXMLParser for my initial implementation but found it to be very confusing to use so with my second implementation I used a utility called XMLDictionary, which essentially turns the RSS feeds into more easily handleable Dictionaries.
 
Last edited:
  • Like
Reactions: 1458279
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.