Expand and collapse static table view - iphone

I have a static table view in my application. This table view is used for preferences.
A section in the table view only has one cell which holds a UISwitch. When this switch is activated, I want to show the section beneath and when it is not, I want to hide the section beneath. All the sections (also the one which should be hidden / shown) is set up using Interface Builder.
Is there any way to hide or show this section when the table view is static as a static table view doesn't have a data source? Should it be easier, I could also agree to use the same section but add / hide rows from this section when the switch is on or off.
EDIT
I have come closer how to do this.
Setting the height of the cells in the section and the height of the footer and header of the section to 0, I can nearly hide the section. I still have some spacing between the section above and the section below that I cannot figure out how to get rid of.
Does anyone have an idea where this extra spacing comes from? See the photo below.
This is the code I use to nearly hide the section.
/* Will display cell */
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.section == 2)
cell.hidden = YES;
else
cell.hidden = NO;
}
/* Height of cell */
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.section == 2)
return 0;
return [super tableView:tableView heightForRowAtIndexPath:indexPath];
}
/* Height of section header */
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
if (section == 2)
return 0;
return [super tableView:tableView heightForHeaderInSection:section];
}
/* Height of section footer */
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section
{
if (section == 2)
return 0;
return [super tableView:tableView heightForFooterInSection:section];
}
This is how the table view looks now. There is still some space I need to hide. The extra space is between the sections labeled "Arbejde" and "Anden".

I got it working using the code in the question. Just set the height to 1.0f instead of 0. It seems that the height only has an effect when it's value is greater than zero.
Reducing the space between sections of the UITableView.

For static cells, I just use this:
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let cell = self.tableView(self.tableView, cellForRowAt: indexPath)
if cell.isHidden {
return 0
} else {
return cell.bounds.size.height
}
}

see this tutorial this may helps you www.cocoanetics.com/2011/03/expandingcollapsing-tableview-sections/

For static cells I found no decent answers either. If you wish to drop sections or expand a cell then this is the code I used.
Create a BOOL Value in your .m
#interface yourClassName (){
BOOL ddMenuButtonPressed;
}
#end
ddMenu means drop down menu.
Next I set the bool value to false in the viewDidLoad method.
ddMenuButtonPressed = false;
I then initialise the ddButton (in storyboard control+drag from your button or switch to your .h file and create action and set name to ddMenuButton) and use this method in my .m file.
- (IBAction)ddMenuShow:(UIButton *)sender
{
if (sender.tag == 0) {
sender.tag = 1;
ddMenuButtonPressed = true;
} else {
sender.tag = 0;
ddMenuButtonPressed = false;
}
//very important that you include these
//they update the view like a viewDidLoad statement without leaving the screen
//if you have any data entry points in your cell like a textfield it **will not** erase that data fortunately
[self.tableView beginUpdates];
[self.tableView endUpdates];
}
Finally we add the following method:
// Handle expanding and minimising cell or section
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
//if the section is 0 (and there is only one cell) and ddMenuButtonPressed hasn't been pressed
if (indexPath.section == 0 && ddMenuButtonPressed == false){
//return the height you have it set as in story board (or a number)
return [super tableView:tableView heightForRowAtIndexPath:indexPath];
}
//if the section is 0 and the ddMenuButton has been pressed
if (indexPath.section == 0 && ddMenuButtonPressed == true){
//change the cell height to 380 or whatever size you want
return 380;
}
//otherwise leave cells as they are
else {
return [super tableView:tableView heightForRowAtIndexPath:indexPath];
}
}
And that's it. No fancy coding, nice and clean (minus the commenting) and simple and you can use this over and over for as many sections as you like.

Related

Two UITableViewCell with different identifier and different height in storyboard

I have two type of cells that I'll be using my UITableView, so I created two prototype cell with different identifier. I manuelly changed the size but when I compile and run, the two cells have same size.
Is there any way to do it through storyboard and without checking every single time
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
I will have around a good 100 cells at least.
UPDATE: in my case I have a tableview with 3 sections, the first one is small, the second and the third one are bigger.
I think your best option is to use that -tableView:heightForRowAtIndexPath: delegate method you listed and return the height you would like.
You could assign tags to each prototype and use an if/else if conditional.
Or if you have a subclassed UITableViewCell for each prototype you could do something like
...
id cell = [tableView cellForRowAtIndexPath:indexPath];
if ([cell isKindOfClass:[CustomCell1 class]]) {
// return aNumber;
} else if ([cell isKindOfClass:[CustomCell2 class]]) {
// return aNumber;
} else {
return aNumber;
}
...
If you know that the sections if what determines the height of the cell, then you can implement it like this:
- (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath: (NSIndexPath*)indexPath {
CGFloat result;
if (indexPath.section == 0) {
result = 160;
} else {
result = 80;
}
return result;
}
Depending on the heights that you need of course.

How to insert a UITableViewCell at the beginning of UITableView

I am trying to set up a UITableView, with x amount of sections and X number of rows per section.
However I would like to add a single row to the top of my UITableView Is there is a way to hardcode this into the view?
I currently return the number of sections and rows per section based off a NSdictionary like so.
- (NSInteger)numberOfSectionsInTableView: (UITableView *)tableView
{
// Return the number of sections.
return [letterDictionary count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// returns the number of rows per section based off each entry in letterDictionary
currentLetter = [sectionLetterArray objectAtIndex:section];
return [[letterDictionary objectForKey:currentLetter] count];
}
You can add a "header" to the tableview.
In your tableview class:
self.tableView.tableHeaderView = yourView;
You cannot just add a row above your UItable within the table. If you just need a row of text why not use UITextField, UILabel or UITextView depending on your needs and just position it above your UItable whereever you like.
If I missunderstood you and you just like to add one row as the very first one in your first section, all you need to do sth like this:
if (section == 0) {
return [[letterDictionary objectForKey:currentLetter] count]+1;
} else
{
return [[letterDictionary objectForKey:currentLetter] count];
}
and ensure that when returning row for indexpath you have also a similar if-statement and return whatever you need for section == 0 and row == 0.
But this first row will certainly scroll away if you scroll down your table view - as said I am not sure exactly what you need.
You could try to customize your tableview section's header...
For example you can use something like this:
YourController.h
-(UIView *)headerView;
YourController.m
-(UIView *)headerView
{
UIView *header = [[UIView alloc] initWithFrame:CGRectZero];
// Place everything you want in your header
// using [header addSubview:yourSubview];
// Finally set header's frame and return it
header.frame = CGRectMake(0.0, 0.0, 320.0, 44.0);
return header;
}
// Use this to return header's height
-(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
if (section == yourSection)
{
return [self headerView].frame.size.height;
}
else
{
return [self sectionHeaderHeight];
}
}
// Use this to return your view
-(UIView *)tableView:(UITableVIew *)tableVIew viewForHeaderInSection:(NSInteger)section
{
if (section == yourSection) // To show the header only on a specified section
{
return [self headerView];
}
else
{
return nil;
}
}
If you change your mind and want to customize below the tableView, your can use the same method changing Header with Footer.
Finally take a look at docs about these methods:
- tableView:viewForHeaderInSection:
- tableView:heightForHeaderInSection:
- tableView:viewForFooterInSection:
- tableView:heightForFooterInSection:
Hope this fits your needs!

How to prevent a UITableViewCell being moved away from its own section?

My UITableView has 3 sections. Only the cells in the 2nd sections are movable:
- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath
{
BOOL canMove = NO;
if (indexPath.section == 1) {
canMove = YES;
}
return canMove;
}
However, a cell (B) in the 2nd section (originally contains A``B``C cells) can be moved to the 1st section (originally contains only the T cell):
 
How can I make sure cells are moved within its own section?
I think this will solve your problem
- (NSIndexPath *)tableView:(UITableView *)tableView targetIndexPathForMoveFromRowAtIndexPath:(NSIndexPath *)sourceIndexPath toProposedIndexPath:(NSIndexPath *)proposedDestinationIndexPath
{
if( sourceIndexPath.section != proposedDestinationIndexPath.section )
return sourceIndexPath;
else
return proposedDestinationIndexPath;
}
From Apple documentation
This method allows customization of the target row for a particular
row as it is being moved up and down a table view. As the dragged row
hovers over a another row, the destination row slides downward to
visually make room for the relocation; this is the location identified
by proposedDestinationIndexPath.

How to remove a Static Cell from a UITableView designed in StoryBoard

The solution is probably very simple, but I couldn't just find it .. !
Working with storyboard (iOS 5), I have a tableViewController, and a designed STATIC tableview with 5 sections, with differents static cell inside each section.
My question is: How to delete a cell programatically in the viewWillAppear?
For example, I have a cell designed for a date
IBOutlet UITableViewCell * cellForDate;
And.. if there's not date, I want to remove my cell.
cellForDate.hidden = true; //Hide the cell, but leave a blank space
Ive tried [tableView deleteRowsAtIndexPaths...] didn't work
Anyone got an idea?
Try to hide the cell before it is shown, in UITableViewDelegate's tableView:willDisplayCell:forRowAtIndexPath: method. That's the final method where you can manipulate the cell's appearance. This however won't remove the space the cell should take, so another thing you can try is to set cell row's height to 0 using the tableView:heightForRowAtIndexPath: method of the same protocol.
Another method, which is most robust, is to devise a method for determining whether you need the date cell in the section and depending on the result, return the proper number of rows in the section, and return other section row cells taking the situation into account. And then reload tableView's data on viewWillAppear.
Hopefully this is a bit more of a universal solution, but it's still not perfect
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [super tableView:tableView cellForRowAtIndexPath:indexPath];
if (cell.hidden) {
return 0;
} else {
return [super tableView:tableView heightForRowAtIndexPath:indexPath];
}
}
With this method you set your IBOutlet bound cell to be hidden, and then when the table tries to display a row, it first checks to see if the cell (using super call to get the cell) should be hidden, if so it returns a height of 0 which effectively removes it from the list. If the cell shouldn't be hidden then it gets the height from the super so whatever would normally happen still happens.
The only issue with this is that start and end cells are responsible for the divider at the top and bottom of the list or group. So if you hide the last cell in a group the bottom divider on the group will be inset slightly like it is on normal rows rather than full width. This is only a problem if you're hiding the top or bottom rows AND you are using a divider AND you care about totally standard display. The best solution in my case was simply to not hide the top or bottom rows. I moved any content that might be hidden to the middle of the group. Alternatively you could just accept that it isn't quite standard, or disable row dividers on your table.
Swift 3
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if indexPath.section == 0 {
cell.isHidden = true
}
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.section == 0 {
return 0
} else {
return super.tableView(tableView, heightForRowAt: indexPath)
}
}
If you want to permanently delete the tableview cell, just call deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationNone] with the an indexPath corresponding to your row. You will also need to decrement the number of rows returned by numberOfRowsInSection:.
However, I was looking for a way to temporarily "delete" unwanted static tableview cells from a static tableview, because I wanted a particular row to be there sometimes, and other times not. I also wanted to be able to update this on demand from outside of the tableview controller.
My case is fairly simple, since it is the first row that was being either shown or hidden. You can generalize to suit your own needs. My tableview controller is my data source delegate.
First, I created a public method in the tableview controller to update the state variables and trigger the redisplay:
- (void)updateSettingsDisplay:(BOOL)hideSetting
{
if (hideSetting == self.hideSetting) {
return; // no need to do anything because it didn't change
}
self.hideSetting = hideSetting;
self.numRows = (hideSetting)? kNumRowsWithSetting : kNumRowsWithoutSetting;
// redisplay only if we're visible
if (!self.viewJustLoaded && (self.navController.visibleViewController == self)) {
[self.tableView reloadData];
}
self.viewJustLoaded = NO;
}
The tableview controller's viewDidLoad looks like:
- (void)viewDidLoad
{
[super viewDidLoad];
// check whether or not to display out-of-coverage tableview cell
self.hideSetting = YES; // initial value; could just as easily be NO
self.viewJustLoaded = YES;
[self updateSettingsDisplay];
}
The tableview's data-source delegate:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return self.numRows;
}
and finally
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// set row to the index of the stored tableView cell that corresponds to the
// cell you want (its location in the static tableview from your storyboard)
NSInteger row = indexPath.row;
if (self.hideSetting) {
// skip drawing setting's tableviewcell (since the setting we're hiding is
// first, we can just increment the row to get the one we want)
++row;
assert(row < kTotalRows); // bounds checking just to convince yourself (remove after testing)
}
// get a new index path since the row field is read-only
NSIndexPath *newIndexPath = [NSIndexPath indexPathForRow:row inSection:indexPath.section];
// grab the cell from super that corresponds to the cell you want
UITableViewCell *cell = [super tableView:tableView cellForRowAtIndexPath:newIndexPath]; // static table
return cell;
}
The trick is that the static cells are always available in [super tableView:tableView cellForRowAtIndexPath:newIndexPath] - so use that for permanent storage of your static tableview cells. Then adjust the number of rows as needed, and map the rows correctly (ie, get the row of the stored cell that corresponds to the cell you want displayed) in your tableview delegate's cellForRowAtIndexPath:.
The updateSettingsDisplay method can be called on your tableview controller by any class that retains it. If the tableview controller is not visible when it is called, it will just update the state and wait until next time it becomes visible to change the display.
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if(indexPath.section == 1) { //For example
return 0
}
}
override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
if(indexPath.section == 1) {
cell.hidden = true
}
}
you might want to look at this
https://github.com/xelvenone/StaticDataTableViewController/
usage
self.hideSectionsWithHiddenRows = YES; //YES, NO
[self cell:self.outletToMyStaticCell1 setHidden:hide];
[self cell:self.outletToMyStaticCell2 setHidden:hide];
[self reloadDataAnimated:YES];
FYI (would comment if I could), if you are overriding a Grouped static UITableView from the Storyboard, then you'll also need to override the Header & Footer views & heights for each section you are modifying/hiding.
I also needed to set the return value for the "GetHeight" methods to something other than zero - implying that a zero value means something else (autolayout, or "not set" or the like).
The below code is in Xamarin iOS C#, but translates directly to the appropriate Obj-C methods:
public override void WillDisplayHeaderView(UITableView tableView, UIView headerView, nint section)
{
if (section == 0)
{
headerView = new UIView(new CGRect(0,0,0,0));
}
}
public override nfloat GetHeightForHeader(UITableView tableView, nint section)
{
if (section == 0)
{
return 1f;
}
return base.GetHeightForHeader(tableView, section);
}
public override void WillDisplayFooterView(UITableView tableView, UIView footerView, nint section)
{
if (section == 0)
{
footerView = new UIView(new CGRect(0, 0, 0, 0));
}
}
public override nfloat GetHeightForFooter(UITableView tableView, nint section)
{
if (section == 0)
{
return 1f;
}
return base.GetHeightForFooter(tableView, section);
}
Just calculate the numberOfRows(inSection) to remove some rows you don't want
You need to put beginUpdates before the delete call and endUpdates after:
[tableView beginUpdates];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:r inSection:s]]];
[tableView endUpdates];
In response to your comment:
- (NSInteger)tableView:(UITableView)tableView numberOfRowsInSection:(NSInteger)section {
NSInteger ret;
switch (section) {
case 0:
// sectionZeroRows doesnt have to be a
// property (an attribute works just fine), but it
// helps with explaining this example.
ret = self.sectionZeroRows;
break;
// ...
}
return ret;
}
- (void)someMethod {
--self.sectionZeroRows;
[tableView beginUpdates];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:0 inSection:0]]];
[tableView endUpdates];
}
// just authed with facebook, and I want to delete the first 3 rows of my 4 row table that are no longer necessary
- (NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section {
if(wantsFacebook)
return 1;
return 4;
}
After committing the updates, the tableView will call the numberOfRowsInSection delegate to get the updated number of rows and it MUST be the correct sum/difference between the rows when you started the insert/delete and the rows when you ended the insert/delete

UITableViewCell expand on click

Lets say we have a custom UITableViewCell
So whenever I click custom button on cell.. it should expand to the some extent (you can say 40 height more...) and when i click again to the same custom button it should collapse to the previous height.
Developer's please guide me.. how can I achieve this task
I'm not going to say anything here to contradict the accepted answer considering it is perfectly correct. However, I am going to go into more detail on how to accomplish this. If you don't want to read through all this and are more interested in playing with the source code in a working project, I've uploaded an example project to GitHub.
The basic idea is is to have a condition inside of the method -tableView: heightForRowAtIndexPath: that determines whether or not the current cell should be expanded. This will be triggered by calling begin/end updates on the table from within -tableView: didSelectRowAtIndexPath: In this example, I'll show how to make a table view that allows for one cell to be expanded at a time.
The first thing that you'll need to do is declare a reference to an NSIndexPath object. You can do this however you want, but I recommend using a property declaration like this:
#property (strong, nonatomic) NSIndexPath *expandedIndexPath;
NOTE: You do not need to create this index path inside viewDidLoad, or any other similar method. The fact that the index is initially nil will only mean that the table will not initially have an expanded row. If you would rather the table start off with a row of your choice expanded, you could add something like this to your viewDidLoad method:
NSInteger row = 1;
NSInteger section = 2;
self.expandedIndexPath = [NSIndexPath indexPathForRow:row inSection:section];
The next step is to head on over to your UITableViewDelegate method -tableView: didSelectRowAtIndexPath: to add the logic to alter the expanded cell index based on the users selection. The idea here is to check the index path that has just been selected against the index path stored inside the expandedIndexPath variable. If the two are a match, then we know that the user is trying to deselect the expanded cell in which case, we set the variable to nil. Otherwise, we set the expandedIndexPath variable to the index that was just selected. This is all done between calls to beginUpdates/endUpdates, to allow the table view to automatically handle the transition animation.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView beginUpdates]; // tell the table you're about to start making changes
// If the index path of the currently expanded cell is the same as the index that
// has just been tapped set the expanded index to nil so that there aren't any
// expanded cells, otherwise, set the expanded index to the index that has just
// been selected.
if ([indexPath compare:self.expandedIndexPath] == NSOrderedSame) {
self.expandedIndexPath = nil;
} else {
self.expandedIndexPath = indexPath;
}
[tableView endUpdates]; // tell the table you're done making your changes
}
Then the final step is in another UITableViewDelegate method -tableView: heightForRowAtIndexPath:. This method will be called after you've triggered beginUpdates once for each index path that the table determines needs updating. This is where you'll compare the expandedIndexPath against the index path that is currently being reevaluated.
If the two index paths are the same, then this is the cell that you wish to be expanded, otherwise it's height should be normal. I used the values 100 and 44, but you can use what ever suits your needs.
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Compares the index path for the current cell to the index path stored in the expanded
// index path variable. If the two match, return a height of 100 points, otherwise return
// a height of 44 points.
if ([indexPath compare:self.expandedIndexPath] == NSOrderedSame) {
return 100.0; // Expanded height
}
return 44.0; // Normal height
}
Implement heightForRowAtIndexPath to calculate the right height. Then in the code for your button, force the table to reevaluate each cell's height with beginUpdates plus endUpdates:
[self.tableView beginUpdates];
[self.tableView endUpdates];
Changes to the tableview cells' heights will automatically be calculated with heightForRowAtIndexPath and the changes will be animated too.
In fact, instead of a button on your cell that does this, you might even just make selecting the cell do this in didSelectRowAtIndexPath.
Instead of using [tableView beginUpdates] and [tableView endUpdates] , I'm using the [tableView reloadRowsAtIndexPath:... withRowAnimation:...] method inside the didSelectRowAtIndexPath method.
I prefer this, because I had some problems with elements that should show, when I expand my UITableViewCell, when I used the begin & end updates methods. Another point is that you can choose between some animations like: Top, Bottom, Left, Right...
I have created an open source library for this. You just implement collapse and expand delegates in your code and voilà! you can also perform any drawings and animations. check out this.
I've made a reusable component that will do exactly what you're talking about. It's pretty easy to use, and there's a demo project.
GCRetractableSectionController on GitHub.
This is Mick's answer but for Swift 4. (IndexPath replaces NSIndexPath, which comes with an empty IndexPath as nil would crash Swift. Also, you can compare two instances of IndexPath using ==)
Declare the expandedIndexPath property.
var expandedIndexPath = IndexPath()
Optional viewDidLoad part.
expandedIndexPath = IndexPath(row: 1, section: 2)
Then the didSelectRow part.
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.beginUpdates()
if indexPath == expandedIndexPath {
expandedIndexPath = IndexPath()
} else {
expandedIndexPath = indexPath
}
tableView.endUpdates()
}
Then the heightForRow part.
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath == expandedIndexPath {
return 100
}
return 44
}
I used Gcamp's source code and made my own version.
1) In a loadView method initialize a mutable array where you will save expanded or non-expanded states of your sections. It is critical to save expanded statuses in a separate array, that is not destroyed while table view scrolls (for instance if you store it in a headerView it will be redrawn and forget weather it was expanded or not). In my case it is _sectionStatuses array.
- (void)loadView
{
// At the beginning all sections are expanded
_sectionStates = [NSMutableArray arrayWithCapacity:self.tableView.numberOfSections];
for (int i = 0; i < self.tableView.numberOfSections; i++) {
_sectionStates[i] = [NSNumber numberWithBool:YES];
}
}
2) Create a custom headerView for a section with a button for expanding. Delegate an action from a button in your headerView to your TableViewController using delegation pattern. You can find suitable images in Gcamp's source code.
3) Create an action to remove or add rows. Here _foldersArray is my structure, that contains all the data. My section's headerView - MCExpandableAccountHeaderView knows it's own section number - I transfer it there when I create header views for each section. It is critical to transfer it to this method, since you have to know which section is now expanded or stretched.
- (void)expandClicked:(MCAccountHeaderView *)sender
{
MCExpandableAccountHeaderView *expandableAccountHeaderView = (MCExpandableAccountHeaderView*)sender;
// Finding a section, where a button was tapped
NSInteger section = expandableAccountHeaderView.section;
// Number of rows, that must be in a section when it is expanded
NSUInteger contentCount = [_foldersArray[section - 1][#"folders"] count];
// Change a saved status of a section
BOOL expanded = [_sectionStates[section] boolValue];
expanded = ! expanded;
expandableAccountHeaderView.expanded = expanded;
_sectionStates[section] = [NSNumber numberWithBool:expanded];
// Animation in a table
[self.tableView beginUpdates];
NSMutableArray* modifiedIndexPaths = [[NSMutableArray alloc] init];
for (NSUInteger i = 0; i < contentCount; i++) {
NSIndexPath* indexPath = [NSIndexPath indexPathForRow:i inSection:section];
[modifiedIndexPaths addObject:indexPath];
}
if (expandableAccountHeaderView.expanded) [self.tableView insertRowsAtIndexPaths:modifiedIndexPaths withRowAnimation:UITableViewRowAnimationFade];
else [self.tableView deleteRowsAtIndexPaths:modifiedIndexPaths withRowAnimation:UITableViewRowAnimationFade];
[self.tableView endUpdates];
// Scroll to the top of current expanded section
if (expandableAccountHeaderView.expanded) [self.tableView scrollToRowAtIndexPath:INDEX_PATH(0, section) atScrollPosition:UITableViewScrollPositionTop animated:YES];
}
4) It is also important to return correct number or rows in a section depending on wheather it is expanded or not.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
BOOL expanded = [_sectionStates[section] boolValue];
return expanded ? [_foldersArray[section - 1][#"folders"] count] : 0;
}
initialize iSelectedIndex = -1; and declare
UITableView *urTableView;
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
return 10; //Section count
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 3; //row count
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
[cell.textLabel setText:[NSString stringWithFormat:#"sec:%d,row:%d",indexPath.section,indexPath.row]];
return cell;
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section{
// adding a label with the tap gesture to the header in each section
headerLabel = [[UILabel alloc]init];
headerLabel.tag = section;
headerLabel.userInteractionEnabled = YES;
headerLabel.backgroundColor = [UIColor greenColor];
headerLabel.text = [NSString stringWithFormat:#"Header No.%d",section];
headerLabel.frame = CGRectMake(0, 0, tableView.tableHeaderView.frame.size.width, tableView.tableHeaderView.frame.size.height);
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(gestureTapped:)];
[headerLabel addGestureRecognizer:tapGesture];
return headerLabel;
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section{
return 50.0; //adjust the height as you need
}
- (void)gestureTapped:(UITapGestureRecognizer *)sender{
UIView *theSuperview = self.view; // whatever view contains
CGPoint touchPointInSuperview = [sender locationInView:theSuperview];
UIView *touchedView = [theSuperview hitTest:touchPointInSuperview withEvent:nil];
if([touchedView isKindOfClass:[UILabel class]])
{
if (iSelectedIndex != touchedView.tag) { //if new header is selected , need to expand
iSelectedIndex = touchedView.tag;
}else{ // if the header is already expanded , need to collapse
iSelectedIndex = -1;
}
[urTableView beginUpdates];
[urTableView endUpdates];
}
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
// Show or hide cell
float height = 0.0;
if (indexPath.section == iSelectedIndex) {
height = 44.0; // Show the cell - adjust the height as you need
}
return height;
}
For me it works to use:
on UITableViewDelegate
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("Did select row: \(indexPath.row).")
tableView.beginUpdates()
tableView.endUpdates()
}
on selectable/expandable UITableViewCell
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
configStyle(selected)
}
Important! tableView.rowHeight is .automatic and UITableViewCell is constraint to enable automatic height calculation, i.e. its height constraint are clearly defined like constraint to top/bottom or height constraints added or label intrinsic content size is used.
To add to 0x7fffffff's answer, I found I needed an extra condition in the if statement within didSelectRowAtIndexPath - thus:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView beginUpdates];
if (self.expandedIndexPath && [indexPath compare:self.expandedIndexPath] == NSOrderedSame) {
self.expandedIndexPath = nil;
} else {
self.expandedIndexPath = indexPath;
}
[tableView endUpdates];
}
Following this medium article on how to expand the cells based on the tap of a button and setting the numbersOfLine for a specific label, I was able to perform the animation using
tableView.beginUpdates()
tableView.performBatchUpdates({
cell.description.numberOfLines = !expanded ? 0 : 3
}, completion: nil)
tableView.endUpdates()
Notice performBatchUpdates is only available in iOS 11⬆️