Click here to Skip to main content
Click here to Skip to main content

Collapsable Table View for iOS

By , 10 Feb 2013
 

Introduction

I was once working on an iPhone application that shows a large number of inputs, grouped into various categories, in a UITableView. To change the value of one of the inputs, the user presses the corresponding row in the table view and alters the value in a separate screen that appears. The table view had a section for each category and each section contained a table cell (row) for each input.

The problem was that the number of inputs became very, very large so that it didn't give the user a very good overview. It was even tedious to scroll from the top of the table to the bottom. 

We decided that the user should be able to collapse and expand the sections (categories) of the table by simply pressing the header of the section. We required that the code that achieves this should be reusable and that it requires the least possible number of changes to the existing code. 

The screenshot below shows what the table view, with its collapsible sections, looks like. 

collapsabletableview/collapsable_table_view.png

Implementation

I figured that the best way to achieve the objectives mentioned above was to create a sub-class of the UITableView class, named CollapsableTableView. This ensures that the code is reusable. If done right, no changes would have to be made to the delegate or data source of the UITableView - they would handle the table view like a regular UITableView. The only necessary change would be to change the class of the UITableView in the xib file to this new sub-class. In order to ensure that the client can use the table view like a regular UITableView, we must try to allow the table view to be manipulated entirely through the interface of the UITableView class, including the UITableViewDelegate, and the UITableViewDataSource protocols.

The collapsible table view must somehow keep track of which sections are collapsed (contracted) and which of them are expanded. Perhaps the most apparent way to do this would be to maintain a set of indices of sections that are expanded, or a boolean array where the value of each index indicates if the corresponding section is expanded or not. However, if we assume that the client of the table view can add and remove sections (which was the case in our scenario), the indices of sections will not remain fixed, so working with the indices would be troublesome at best. We must therefore find a different identifier for sections. We could use the header text of sections for this purpose. Of course, this assumes that the header text of a section uniquely identifies that section and that its header text remains constant, but given the constraint of having to stick to the interface of the UITableView class, this is probably the best we can do. This also assumes that the client implements the tableView:titleForHeaderInSection: selector of the UITableViewDelegate protocol for all of the table cells. For our project, this was the case. In the Using the code section, we explain how our class also supports clients that implement the tableView:viewForHeaderInSection: selector.

For easier management of the header views, we create a UIViewController class, named CollapsableTableViewHeaderViewController. For this class, there are two xibs. One xib is used for a table with a plain layout, and the other one is used for a table with a grouped layout. This class contains IB outlets for all the labels in the view that can be manipulated. It stores a boolean value indicating if the section is collapsed or not. This view controller class also ensures that its view notifies us when the user taps it, so that the CollapsableTableView can take the necessary action.

Here is the contents of the .h file of CollapsableTableViewHeaderViewController:

#import <UIKit/UIKit.h>
#import "TapDelegate.h"
#import "CollapsableTableViewTapRecognizer.h"


@interface CollapsableTableViewHeaderViewController : UIViewController 
{
    IBOutlet UILabel *collapsedIndicatorLabel,*titleLabel,*detailLabel;

    CollapsableTableViewTapRecognizer* tapRecognizer;

    BOOL viewWasSet;
    id<TapDelegate> tapDelegate;

    NSString* fullTitle;
    BOOL isCollapsed;
}

@property (nonatomic, retain) NSString* fullTitle;
@property (nonatomic, readonly) UILabel* titleLabel;
@property (nonatomic, retain) NSString* titleText;
@property (nonatomic, readonly) UILabel* detailLabel;
@property (nonatomic, retain) NSString* detailText;
@property (nonatomic, assign) id<TapDelegate> tapDelegate;
@property (nonatomic, assign) BOOL isCollapsed;

@end

collapsedIndicatorLabel is a small label displaying a '–' or a '+' depending on whether the section is collapsed or not. When the value of isCollapsed is changed, the text of the collapsedIndicatorLabel is set to "–" or "+", accordingly. titleLabel is the label containing the text of the header and detailLabel shows optional detail text to the right of the title.

Here follows the definition of the TapDelegate protocol:

#import <UIKit/UIKit.h>

@protocol TapDelegate

- (void) view:(UIView*) view tappedWithIdentifier:(NSString*) identifier;

@end

The view:tappedWithIdentifier: selector is called when a header view is tapped and CollapsableTableView implements the TapDelegate protocol so that it can collapse or expand the corresponding header in response to this. When the selector is called, it will be called with the header view for the view parameter and the title string of the header for the identifier parameter, so that the CollapsableTableView can do a look-up to determine whether the header is currently collapsed or not, and what its current section index is.

In the first published implementation of this project, this selector was called by the CollapsableTableViewHeaderViewController of the corresponding header view. That worked because in that version the CollapsableTableView stored (and thus retained) the CollapsableTableViewHeaderViewControllers of all of its sections. However, to make the implementation more memory-efficient - especially for tables with many sections - the CollapsableTableView was changed so that it no longer does this. As a result, it turns out that the CollapsableTableViewHeaderViewController of a header view is released from memory shortly after the header view appears in the table (the header UIView still remains in memory as long as it's visible in the table). This means that when the header view is tapped there will probably be no CollapsableTableViewHeaderViewController to call the TapDelegate selector.

Before we look for a solution to this problem, let's see how the tap of a header view was previously detected and handled in CollapsableTableViewHeaderViewController.m.

- (void) setView:(UIView*) newView
{
    if (viewWasSet)
    {
        [self.view removeGestureRecognizer:tapRecognizer];
        [tapRecognizer release];
    }
    [super setView:newView];
    tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self 
        action:@selector(headerTapped)];
    [self.view addGestureRecognizer:tapRecognizer];
    viewWasSet = YES;
}

- (void) headerTapped
{
    [tapDelegate viewTapped:self.view ofViewController:self];
}

So we had overridden the setView: method of the UIViewController class in order to add a UITapGestureRecognizer to the UIView that is assigned to the CollapsableTableViewHeaderViewController. This UITapGestureRecognizer is configured to call a method of the CollapsableTableViewHeaderViewController whenever the header view is tapped. In the new code this technique no longer works since the CollapsableTableViewHeaderViewController will often have been deallocated by the time the user taps a header.

The one solution to this problem that is probably the most obvious is to configure the UITapGestureRecognizer to call a selector in an object that will not have been deallocated when the user taps the header view. Some choices for this object are:

  • CollapsableTableView
  • UIView
  • UITapGestureRecognizer

The second choice will not work, since we don't have any control over where the UIView that is passed into the setView: method comes from (that is, we cannot sub-class UIView in order to add an extra method to it; perhaps we could wrap the passed-in UIView object in an instance of a sub-class of UIView of our own, but let's not go there!). Adding a method to CollapsableTableView is an option, although adding a method with no parameters will not do, since the CollapsableTableView would not know which header has been tapped. However, in the documentation of UITapGestureRecognizer, we see that the alternative selector-type is a selector that takes the UITapGestureRecognizer object as a parameter. However, we would have to sub-class UITapGestureRecognizer in order to add a property that stores the title string of the header. So if we must sub-class UITapGestureRecognizer, it would probably be more elegant to go with the third option and configure the UITapGestureRecognizer to call a selector within itself. This is the approach taken in the implementation: we use a sub-class of UITapGestureRecognizer, which we call CollapsableTableViewTapRecognizer, and define it like this:

#import <Foundation/Foundation.h>
#import "TapDelegate.h"

@interface CollapsableTableViewTapRecognizer : UITapGestureRecognizer
{
    id<TapDelegate> tapDelegate;
    
    NSString* fullTitle;
    UIView* tappedView;
}

@property (nonatomic, assign) id<TapDelegate> tapDelegate;
@property (nonatomic, retain) NSString* fullTitle;
@property (nonatomic, assign) UIView* tappedView;

- (id) initWithTitle:(NSString*) theFullTitle andTappedView:(UIView*) 
       theTappedView andTapDelegate:(id<TapDelegate>) theTapDelegate;

@end  

Within the initWithTitle:andTappedView:andTapDelegate: method, the CollapsableTableViewTapRecognizer object is configured to call the private method headerTapped when the view is tapped.

- (void) headerTapped
{
    [tapDelegate view:tappedView tappedWithIdentifier:fullTitle];
} 

Let's return to CollapsableTableView now. When it gets a header title of a section from the client, it needs to be able to do a look-up to see if the header is collapsed, and what the section index of the header is. For this, we maintain two separate NSMutableDictionary objects: one that maps a header title to a boolean value indicating if the header is collapsed, and one that maps a header title to an integer giving the section index of the header. It also comes in handy to have a dictionary that we can use to look up the header title of the section at a specified index (of course, this dictionary will have to be updated whenever the client adds or removes a section from the table).

So, how will the CollapsableTableView actually collapse and expand sections? Well, a collapsed section will simply have 0 rows, so even though the client will return the normal number of rows for the section, CollapsableTableView will return 0 for the number of rows of a collapsed section, or the number returned by the client for an expanded section. This suggests that CollapsableTableView needs to intercept the calls to the tableView:numberOfRowsInSection: method. It must also return a view of a CollapsableTableViewHeaderViewController for each section, so it must also intercept calls to the tableView:viewForHeaderInSection: method. So in order for CollapsableTableView to be able to respond to both these selectors, it must implement the UITableViewDelegate and the UITableViewDataSource protocols and at run-time set its delegate and data source properties to... itself! Many calls to the selectors of these protocols, however, must be forwarded to the client, so CollapsableTableView stores references for the real delegate and data source so that they can be consulted for these cases.

- (void) setDelegate:(id <UITableViewDelegate>) newDelegate
{
    [super setDelegate:self];
    realDelegate = newDelegate;
}

- (void) setDataSource:(id <UITableViewDataSource>) newDataSource
{
    [super setDataSource:self];
    realDataSource = newDataSource;
}

Here is the interface file of CollapsableTableView:

#import <Foundation/Foundation.h>
#import "TapDelegate.h"

#define COLLAPSED_INDICATOR_LABEL_TAG 36
#define BUSY_INDICATOR_TAG 37
 

@interface CollapsableTableView : 
   UITableView <UITableViewDelegate,UITableViewDataSource,TapDelegate>
{
    id<UITableViewDelegate> realDelegate;
    id<UITableViewDataSource> realDataSource;
    id<CollapsableTableViewDelegate> collapsableTableViewDelegate;
    
    ...
}

@property (nonatomic,assign) id<CollapsableTableViewDelegate> collapsableTableViewDelegate;
@property (nonatomic,retain) NSString* collapsedIndicator;
@property (nonatomic,retain) NSString* expandedIndicator;
@property (nonatomic,assign) BOOL showBusyIndicator;
@property (nonatomic,assign) BOOL sectionsInitiallyCollapsed;
@property (nonatomic,readonly) NSDictionary* headerTitleToIsCollapsedMap;

- (void) setIsCollapsed:(BOOL) isCollapsed forHeaderWithTitle:(NSString*) headerTitle;
- (void) setIsCollapsed:(BOOL) isCollapsed forHeaderWithTitle:(NSString*) 
                               headerTitle andView:(UIView*) headerView;
- (void) setIsCollapsed:(BOOL) isCollapsed forHeaderWithTitle:(NSString*) 
         headerTitle withRowAnimation:(UITableViewRowAnimation) rowAnimation;
- (void) setIsCollapsed:(BOOL) isCollapsed forHeaderWithTitle:(NSString*) 
         headerTitle andView:(UIView*) headerView 
         withRowAnimation:(UITableViewRowAnimation) rowAnimation;

@end   

The implementation of CollapsableTableView pretty much follows from the discussion up to this point. The next paragraphs briefly explain the purposes of the public properties and methods of the class.

The collapsableTableViewDelegate property can be set to an object that implements the CollapsableTableViewDelegate property, so that that object will be notified whenever a section will collapse or expand, and when it finished collapsing or expanding.

The default collapsed and expanded indicators (that default to a '+' and a '–' respectively) can be set using the collapsedIndicator and expandedIndicator properties.
The showBusyIndicator property has a default value of YES, and if set, causes an activity indicator view (spinner) (located in the header view as the subview having the tag number defined by BUSY_INDICATOR_TAG) to animate on a header view when collapsing or expanding its section takes longer than 0.5 seconds.
The sectionsInitiallyCollapsed property has a default value of NO, and controls if new sections will be initially be collapsed or not. 

The headerTitleToIsCollapsedMap property supplies an NSDictionary that maps a header's title string to an NSNumber object that contains a boolean indicating if the header is collapsed.

The setIsCollapsed:forHeaderWithTitle:... methods are to be used to programmatically collapse or expand sections. If the client has a reference to the UIView of the corresponding header, it can call one of the methods containing andView: with that UIView as parameter. If any one of the two other methods are called, the corresponding section (and header view) will have to be reloaded and the animation will sometimes be less nice than when one of the ...andView: methods are used. 

Using the code

The source files that need to be added to an Xcode project in order to use the CollapsableTableView class are those in the CollapsableTableView folder in the Zip file (Download CollapsableTableViewTestProject.zip - 89.3 KB). 

CollapsableTableView can be used exactly like a regular UITableView, as long as the client implements the tableView:titleForHeaderInSection: selector (opposed to tableView:viewForHeaderInSection:) for all the table cells. The only necessary change to be made is to change the class of the UITableView in the xib to CollapsableTableView. To do this, open the xib file, select the UITableView, open the Identity Inspector and type "CollapsableTableView" next to the Class field.

The implementation of CollapsableTableView does also allow the use of tableView:viewForHeaderInSection:, but here it doesn't have access to the title string of the header, which it normally uses as the identifier of the header. Instead, it uses the string "Tag %i", where %i is the value of the tag property of the view that is returned (if tag is 0, but the section index is not 0, this number defaults to the section index in CollapsableTableView). This means that if the client returns views (instead of header text strings) for some cells, and if it can add and remove sections, it must assign a unique tag number to the view corresponding to each section.

If the client returns views for some cells, those views can contain a label that indicates if the header is collapsed. Simply set the tag property of that label to the value COLLAPSED_INDICATOR_LABEL_TAG as defined in CollapsableTableView.h. The CollapsableTableView will then set the text of that label to '+' or '–' whenever the section is collapsed or expanded  (unless the collapsedIndicator or expandedIndicator properties have been set to different strings).

The client of the CollapsableTableView can be unaware of the fact that it is not working with a regular UITableView, but if it does know that the UITableView is a CollapsableTableView, it can cast the object to the latter type and use the headerTitleToIsCollapsedMap property to determine which sections are collapsed and the setIsCollapsed:forHeaderWithTitle: methods to programmatically collapse or expand sections. 

As was mentioned in the Implementation section, CollapsableTableView also allows for detail-text to be displayed to the right of the title of a header. To make use of this feature, in tableView:titleForHeaderInSection:, return a string of the form "Header Text|Detail Text".

History

  • 2011/08/13
    • Initial version.
  • 2011/10/29
    • With the previous version, in iOS 5, the headers all had a height of 0. The tableView:heightForHeaderInSection: selector now finds the appropriate header view and asks it for its height directly, which fixes the problem.
    • Support for multiple line headers was added. If the numberOfLines property of the label in the appropriate header view controller xib is set to 0, and the header's text doesn't fit into one line, the label will break the text up in as many lines as necessary, and the header view controller will set the height of the label and the header view so that all the lines will fit (this is done in the setTitleText: selector in CollapsableTableViewHeaderViewController.m). The number of lines of a header can be limited by setting the label's numberOfLines property to the maximum number of lines.
    • 1st.osama pointed out that the setIsCollapsed:forHeaderWithTitle: selector did not have an effect until after the entire section of the corresponding header had been reloaded explicitly. This has been fixed.
    • When the last section is collapsed and it is expanded, the table view scrolls down to at most the fifth row of that section so that the user can see that some rows have appeared.
  • 2011/11/05
    • Fixed bug that occurred with iOS 4 which caused header titles of a plain-style table view to disappear.
  • 2011/11/27
    • CollapsableTableView was changed so that it no longer stores all the CollapsableTableViewHeaderViewController objects of its sections. The purpose of this change was to make the implementation more memory efficient, especially for tables with many sections. This was quite a dramatic change, but the relevant parts in the article have been updated.
    • The getHeaderTitleToIsCollapsedMap method of CollapsableTableView was replaced by the read-only property headerTitleToIsCollapsedMap.
    • Custom header views can now contain a label with the magic tag value of 36 (as defined by the constant COLLAPSED_INDICATOR_LABEL_TAG in CollapsableTableView.h) whose text will be updated to '+' or '–' whenever the corresponding header is collapsed or expanded.
  • 2012/01/26
    • Per alaska22's request, in addition to the initWithCoder: method, CollapsableTableView now also overrides the init, initWithFrame:, and initWithFrame:style: methods to do the necessary initializations. This is so that the CollapsableTableView can be constructed programmatically as well, as opposed to having it constructed by a xib.
  • 2012/02/12
    • As suggested by magikcm in the comments, I've implemented an optimization for row insertions when a section is expanded. I did this by implementing the "dishonest proxy data source" strategy as explained in the first answer to the question of this post.
  • 2012/08/10
    • Previously, when the table view needed to know the height of a header view, the collapsable table view would actually create that header in order to determine and return its height. This caused large delays whenever the table view needed to recompute its total height, since the heights of all the headers are queried during this process. This happened, for example, when expanding or collapsing a section. The delay was especially noticeable with a large number of sections.

      The collapsable table view now caches the heights of all header views. This virtually eliminates the excessive delay that occurred when collapsing or expanding sections.

    • Aesthetic improvement: the collapsed/expanded indicator of the header views of expanded sections now show a longer dash character instead of the regular, short dash character.
  • 2012/11/14
    • When a section is empty, its collapsed/expanded indicator is not shown.
    • The collapsed- and expanded indicators can now be set to custom strings through the collapsedIndicator and expandedIndicator properties of CollapsableTableView.
  • 2012/12/23
    • If the client provides both views and titles for headers (by implementing tableView:viewForHeaderInSection: and tableView:titleForHeaderInSection:), then views are preferred whenever the view returned for the section is not nil.
      Footers are handled similarly. This behaviour corresponds to the way that UITableView is implemented.
    • Added support for section footers.
    • Fixed a crash that occurred when the last section contained no rows and it is tapped.
    • When a section takes more than 0.5 seconds to collapse or expand, an activity indicator appears on the header view. (Whew! User interface programming using multiple threads is tricky!)
      This feature can be switched on or off by setting the showBusyIndicator property (by default it is on). To enable this behaviour in a custom header view, add a UIActivityIndicatorView to the header view with a tag value of BUSY_INDICATOR_TAG (defined in CollapsableTableView.h as 37).
    • A CollapsableTableViewDelegate can be assigned to the CollapsableTableView, so that the delegate can be notified whenever a section will begin to collapse or expand, or when it had finished collapsing or expanding.
    • The new property sectionsInitiallyCollapsed of CollapsableTableView controls if new sections are initially collapsed or not. The default value is NO.
  • 2013/01/18
    • Fixed crash that occured when scrollToRowAtIndexPath:…, or selectRowAtIndexPath:…, or deselectRowAtIndexPath:… was called on a row inside a collapsed section.
    • Fixed the methods mentioned above so that when they are called with animated=YES, the effect will be immediate (synchronous).
    • Fixed a crash that occured occasionally when collapsing and expanding sections.
    • Implemented remove-rows optimization. Thanks to this, collapsing sections with very large numbers of rows should be faster now.
    • CollapsableTableView has been made compatible with static cells in Storyboard.
  • 2013/02/10
    • Fixed crash that occurred when double-tapping the header of a large section. 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Bernhard Häussermann
Software Developer Microworks, Blue Owl Software
South Africa South Africa
Member
Bernhard holds an Hons BSc in Computer Science and is a full-time software developer at Microworks, where he works mainly with Java, C# (.NET), and Pentaho Data Integration (aka Kettle).
 
After-hours he does iOS and Mac OS X development for Blue Owl Software. He also enjoys playing the piano.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionChange cell view.memberDavid DelMonte12 Feb '13 - 17:37 
Hi Bernhard.. I am almost done integrating the Coll. Table View into my app. Again, so as not to mess up, where would be the best place to change the look and style of the cells? Thanks again. David
AnswerRe: Change cell view.memberBernhard Häussermann12 Feb '13 - 19:09 
Hi David,
 
you would customize your cells in the tableView:cellForRowAtIndexPath: delegate method.
This works in exactly the same way as though you were using a regular UITableView. Instead of using one of the standard cell views available, it is possible to load a cell from a xib. There are many tutorials available that explain how to do this.
GeneralRe: Change cell view.memberDavid DelMonte12 Feb '13 - 21:02 
Bernhard, sorry to be a nuisance. What I want to do is to change cell appearance from your standard letters on a grey background to a white cell (that looks like a label with white background.. Could I not simply change the NIB? Sorry, it's 3am here and this is enough for today. I wish there was a way to send small images this board.
AnswerRe: Change cell view.memberBernhard Häussermann12 Feb '13 - 22:34 
Hi David,
 
if you want to change the cells of the rows of the table, my answer remains the same.
However, if you want to provide your own custom views for the headers, you must implement the tableView:viewForHeaderInSection: method in your delegate. The "Using the Code" section of the article provides a few notes on this.
 
Specifically, have a look at the paragraph that starts with "If the client returns views for some cells...". On that point, please note that that paragraph contains an error - it should start with "If the client returns views for headers". I will fix that in a future update.
GeneralMy vote of 5memberPaul E. Bible11 Feb '13 - 2:47 
Nice work!
QuestionUse in complex table view with editable custom cellsmemberDavid DelMonte9 Feb '13 - 5:21 
Hi Bernhard, this is a great codebase that you've built. THANK YOU.
 
I would like to use this in an app that requires user input to cells. As in your description, there can be a lot of information, and collapsible sections is a good solution.
 
So, first I wonder if you agree that this is a good approach for this type of project (there will be about 50 rows).
 
Second, rather than me "stomping" around in the code, where should I implement SelectRowAtIndexPath? I've tried in CollapsableTableView.m but I guess I'm doing something wrong.
 
Finally, it would be great to add a keyboard accessory to facilitate moving from row to row - section to section.
 
Warm regards
 
David
David DelMonte

AnswerRe: Use in complex table view with editable custom cellsmemberBernhard Häussermann9 Feb '13 - 5:41 
Hi David,
 
thanks for your feedback!
I believe that for showing that number of fields the table view with collapsible sections is a good choice. However, it sounds like you're trying to make the content shown in your table view cells to be editable directly (like text fields). That approach is fine for switches and maybe sliders. However, to make a table view cell's text value directly editable might be difficult to achieve, and is not conventional. Typically, a text field is shown on a separate screen when the table view cell is tapped, and I would recommend the same approach.
 
To answer your second question: you are not supposed to change anything in CollapsableTableView.m . Use the source code as-is, except if you discover a bug that you need to fix. You will see that selectRowAtIndexPath:animated:scrollPosition: is implemented and you can call this method as you would on a regular UITableView.
GeneralRe: Use in complex table view with editable custom cellsmemberDavid DelMonte9 Feb '13 - 6:29 
Hi Bernhard, thanks for the fast response and for the good advise. It sounds like a great idea to make a separate view for the text fields, one that morphs depending on the context. I don't want to create a view for each row! and thanks for letting me think out loud.
GeneralMy vote of 5memberDavid DelMonte9 Feb '13 - 5:15 
The explanation of the code is worth five votes by itself. The code is well written and succinct. Well done.
BugApp still crashes occasionally in ios 6 simulator..memberHarold Ian22 Jan '13 - 22:05 
Good day sir!
 
I have downloaded the latest update and integrate it with my project. It is running smoothly. It crashes when you purposely fast tap the header. I mean it's not really the way you do it but I just did it to test it. I think the problem is when the header was has not yet finished loading its sub-items then you collapse it. Aside from that, as I said, were all great. Thank you very much for this sir!Thumbs Up | :thumbsup:
 
Cheers!
GeneralRe: App still crashes occasionally in ios 6 simulator..memberBernhard Häussermann4 Feb '13 - 8:31 
Hi Harold,
 
thank you for the feedback! I have also discovered the crash now and luckily it is easy to fix.
 
All you need to do is to add these two lines at the beginning of the toggleSectionCollapsedForTitle:headerView:withRowAnimation: method in CollapsableTableView.m:
 
if (temporaryRowCountOverrideSectionIdx!=-1)
    return;
 
This will ensure that when a header is tapped while a section is still being collapsed or expanded, the tap would be ignored, preventing a crash.
 
I will include this fix in the next published version of the code.
QuestionUsing with a subclass of UITableViewController [modified]memberJoel.Rosi.Schwartz13 Jan '13 - 2:20 
Hi,
 
I would like to use Collapsable Table View while subclassing a UITableViewController because I want to use Static Table Cells which are only supported in StoryBoard inside the UITableViewController. I have successfully created the controller subclass and change the tableview to CollapsableTableView. The view initialises and shows but as as soon as I try to expand on of the sections an exception is thrown in
- (void) toggleSectionCollapsedForTitle:(NSString*) headerTitle headerView:(UIView*) view withRowAnimation:(UITableViewRowAnimation) rowAnimation
at this line
        [super insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationFade];
the exception is
2013-01-13 13:04:53.236 ThatsIt[20408:c07] *** Assertion failure in -[CollapsableTableView _configureCellForDisplay:forIndexPath:], /SourceCache/UIKit_Sim/UIKit-2380.17/UITableView.m:5471
 
I had this all running correctly earlier by extending a plain UIViewController and adding the delegates. None of my delegate methods have changed. The only real changes I made were in the Storyboard changing the Table View Content to Static and the Sections from 1 to 6.
 
Any suggestions on how to debug this?
 
Many thanks,
Joel

modified 13 Jan '13 - 8:28.

AnswerRe: Using with a subclass of UITableViewControllermemberBernhard Häussermann13 Jan '13 - 9:26 
Hi Joel,
 
I have not yet played with static table cells and I do not know how they work. Therefore, I don't know if collapsable table view supports that.
 
I would like to look into it soon. Tomorrow afternoon/evening I need to do some work on another project, so I will try for Tuesday afternoon.
GeneralRe: Using with a subclass of UITableViewControllermemberJoel Rosi-Schwartz13 Jan '13 - 12:55 
That would be great, many thanks.
AnswerRe: Using with a subclass of UITableViewControllermemberBernhard Häussermann16 Jan '13 - 8:00 
Hi Joel,
 
I did some tests with static cells. It looks like when static cells are used, the sequence of methods that the table view calls on its delegates is different. For example, tableView:titleForHeaderInSection: is called before tableView:viewForHeaderInSection: (even though views are supposed to be prefered over titles WTF | :WTF: ).
 
In order to get collapsable table view to work with static cells, I basically had to make two changes:
  • Implement tableView:titleForHeaderInSection: and make it return @" ".
  • Initialize headerHeightArray in tableView:viewForHeaderInSection: when necessary (similarly for footerHeightArray).
 
You can download the updated source code here. The changes since the newest version of the article include:
 
  • Fixed crash that occured when scrollToRowAtIndexPath:…, or selectRowAtIndexPath:…, or deselectRowAtIndexPath:… was called on a row inside a collapsed section.
  • Fixed the methods mentioned above so that when they are called with animated=YES, the effect will be immediate (synchronous).
  • Fixed a crash that occurred occasionally when collapsing and expanding sections.
  • Implemented remove-rows optimization. Thanks to this, collapsing sections with very large numbers of rows should be faster now.
  • CollapsableTableView should now be compatible with static cells in Storyboard.
 
(an article update is about to follow).
 
Try out the new source code to see if works for you.
GeneralRe: Using with a subclass of UITableViewControllermemberJoel Rosi-Schwartz17 Jan '13 - 0:24 
Many thanks for this Bernhard -- Joel
QuestionIOS 4.3 supportmemberhydro408 Jan '13 - 2:57 
Did you try recently your project on iOS 4.3? I downloaded the current version (previous version is from almost a year ago) and I see a black screen... Do you have any clues why it is not working?
 
Besides that, it would be nice if you also clone your project on Github. That will be easier to contribute or understand the changes.
AnswerRe: IOS 4.3 supportmemberBernhard Häussermann13 Jan '13 - 5:56 
Hello hydro,
 
Sorry for the late reply. I do not always receive an email from CodeProject when there is a new comment. Mad | :mad:
 
I cannot try running the project on iOS 4.3. It looks as though iOS 4 is not available as a Simulator for Xcode 4.5.2. The only device I have that still runs iOS 4 is my 2nd-gen iPod Touch, and Xcode no longer supports it.
 
I cannot tell why the project will not run on iOS 4.3. It's a long shot, but try changing the compiler back to the old LLVM GCC 4.2 compiler in the target's build settings.
 
Now that you're mentioning it, I noticed that while CodeProject did publish my most recent article, for some reason the accompanying version of the source code is still pending! I need to have a word with these guys!
 
I'm not sure this will make a difference, but I have made the most recent code available for you to download here.
AnswerRe: IOS 4.3 supportmemberKarstenK10 Feb '13 - 21:45 
I wont support iOS below 5.0 because these changes where so important, as customization of controls.
 
So you should upgrade Blush | :O
Press F1 for help or google it.
Greetings from Germany

QuestionEmpty tablesmemberMassimo Colurcio19 Dec '12 - 6:37 
Hi Bernhard,
 
I've found (and maybe solved) a problem that occurs when you click on an header of an empty table (I mean with no rows, only with the sole header).
 
When you first tap on the header the (empty) table should collapse (you see nothing 'cause the table has no rows); the second tab should expand the table but the error occurs.
After some tests I have noticed that, in case of empty table, the indexPath.row in method scrollTorowAtIndexPath is -1.
A simple check for indexPath.row != -1 before [super scrollToRowAtIndexPath...] solves the problem.
 
HTH,
Massimo
AnswerRe: Empty tablesmemberBernhard Häussermann19 Dec '12 - 10:18 
Hi Massimo,
 
thank you for the feedback!
I believe the bug occurs when the header of the last section of the table is tapped, and that section is empty.
The bug is caused by the code that scrolls up to 5 rows down when the last section is expanded. Obviously this is not possible when the section does not have any rows.
 
I have added a clause in that statement's if-condition to ensure that the section is not empty.
Questionshow an activity indicator when a header is tappedmemberMember 958480118 Dec '12 - 21:51 
hello. its me again sir!
 
i am much grateful with your collapsable table. but due to the fact that it has a mini lag after a header is tapped, i want to add an activity indicator at the left side of the nav bar. how can i achieve this?
 
thank you so much sir!
GeneralRe: show an activity indicator when a header is tappedmemberBernhard Häussermann18 Dec '12 - 23:04 
Hi Member,
 
that is a tricky request. The only way I can think of to achieve this is to update the collapsable table view so that it calls a delegate whenever sections are collapsed or expanded.
 
I will look into implementing this as it might become a useful feature for other developers as well. I'll keep you updated on this.
GeneralRe: show an activity indicator when a header is tappedmemberMember 958480119 Dec '12 - 14:06 
Thank you so much for your immediate reply sir. I will frequently visit this site for future updates.
AnswerRe: show an activity indicator when a header is tappedmemberBernhard Häussermann21 Dec '12 - 8:38 
Hi Member,
 
I have implemented the following two features in the newest version of the code:
  1. When a section takes more than 0.5 seconds to collapse or expand, an activity indicator appears on the header view. (Whew! User interface programming using multiple threads is tricky!) This feature can be switched on or off by setting the showBusyIndicator property (by default it is on). To enable this behaviour in a custom header view, add a UIActivityIndicatorView to the header view with a tag value of BUSY_INDICATOR_TAG (defined in CollapsableTableView.h as 37).
  2. A CollapsableTableViewDelegate can be assigned to the CollapsableTableView via its collapsableTableViewDelegate property, so that the delegate can be notified whenever a section will begin to collapse or expand, or when it had finished collapsing or expanding.
So you can now indicate activity in two ways:
  1. let the header view display an activity indicator (spinner) after 0.5 seconds (if you don't want this, just set showBusyIndicator to NO), or
  2. implement some or all of the CollapsableTableViewDelegate methods in order to indicate activity whenever a section is collapsed or expanded.
I will publish the code on the article soon. In the meanwhile, you can download it here.
 
When you're testing the code, please check if it still crashes or becomes unresponsive, as you had reported on 22 November. I was not able to produce the problem or figure out why it happens, so I want to establish if the issue is still present.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130523.1 | Last Updated 10 Feb 2013
Article Copyright 2011 by Bernhard Häussermann
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid