iOS - Expand/Collapse UITableView section with animations - ios

I want to expand/collapse my UITableView sections with animations. I used this answer and it works now if I call self.tableView.reloadData(). But I want that when I tap on my custom UITableView- header , the cells of the section should slide down/up with a nice animation. I tried to use self.tableView.beginUpdates() and self.tableView.endUpdates(), but I get this error:
Invalid update: invalid number of rows in section 0. The number of rows contained in an
existing section after the update (8) must be equal to the number of rows contained in that
section before the update (0), plus or minus the number of rows inserted or deleted from
that section (0 inserted, 0 deleted) and plus or minus the number of rows moved into or out
of that section (0 moved in, 0 moved out).
Here's some code. The method that is called when I tap on the section:
func expand(sender:UITapGestureRecognizer){
let tag = (sender.view?.tag)!
self.tableView.beginUpdates()
if ausgeklappt[tag] { ausgeklappt[tag] = false }
else { ausgeklappt[tag] = true }
self.tableView.endUpdates()
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// Return the number of rows in the section.
let keyDerSection = sortSpieleDict.keys.array[section]
let arrayDerSection = sortSpieleDict[keyDerSection]!
if ausgeklappt[section] == false { return 0 }
else { return arrayDerSection.count }
}
Thanks.

Thanks to iOS_DEV I found a solution:
Just one line of code did the trick. I just replaced the beginUpdates() and endUpdates() with the reloadSections() method. Now it works fine!
func expand(sender:UITapGestureRecognizer){
let tag = (sender.view?.tag)! // The tag value is the section of my custom UITabelView header view.
if ausgeklappt[tag] { ausgeklappt[tag] = false }
else { ausgeklappt[tag] = true }
// The next line did the trick!
self.tableView.reloadSections(NSIndexSet(index: tag), withRowAnimation: UITableViewRowAnimation.Automatic)
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// Return the number of rows in the section.
let keyDerSection = sortSpieleDict.keys.array[section]
let arrayDerSection = sortSpieleDict[keyDerSection]!
if ausgeklappt[section] == false
{
return 0
}
else
{
return arrayDerSection.count
}
}

i used a NSMutableSet to keep track of clicked headers. I placed a UIButton on every header which responded to the following event:
#pragma mark - Header Clicked
-(void) headerClicked:(UIButton *) sender{
if ([set_OpenIndex containsObject:[NSNumber numberWithUnsignedInteger:sender.tag]]) {
[set_OpenIndex removeObject:[NSNumber numberWithUnsignedInteger:sender.tag]];
[tableview_Main reloadSections:[NSIndexSet indexSetWithIndex:sender.tag] withRowAnimation:UITableViewRowAnimationAutomatic];
}
else{
if (set_OpenIndex.count > 0) {
//--- a header is opened already, close the previous one before opening the other
[UIView animateWithDuration:0.5 animations:^{
[set_OpenIndex enumerateObjectsUsingBlock:^(id obj, BOOL *stop){
[set_OpenIndex removeObject:obj];
[tableview_Main reloadSections:[NSIndexSet indexSetWithIndex:[obj integerValue]] withRowAnimation:UITableViewRowAnimationAutomatic];
}];
} completion:^(BOOL finished){
[set_OpenIndex addObject:[NSNumber numberWithUnsignedInteger:sender.tag]];
[tableview_Main reloadSections:[NSIndexSet indexSetWithIndex:sender.tag] withRowAnimation:UITableViewRowAnimationAutomatic];
}];
}
else{
[set_OpenIndex addObject:[NSNumber numberWithUnsignedInteger:sender.tag]];
[tableview_Main reloadSections:[NSIndexSet indexSetWithIndex:sender.tag] withRowAnimation:UITableViewRowAnimationAutomatic];
}
}
}
And just set up the number of rows as follows:
-(NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
if ([set_OpenIndex containsObject:[NSNumber numberWithInteger:section]]) {
return 5; // or what ever is the number of rows
}
return 0;
}
I have written this code in Objective-C as i don't have much knowledge of swift yet. This is just for the logic. Please convert the code according to your need.
Note: don't forget to set the tag of the UIButton in header according to the section number.

Basically tableview beginupdates & endplates is used when there is a change in the section-row model and we update delete or insert few rows.
However your problem must be from the cellforHeight or cellforRowAtIndex where you need to put the section in switch case.
Thanks

- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self initialization];
}
#pragma mark - Initialization
-(void)initialization
{
arrayForBool=[[NSMutableArray alloc]init];
dic_data =[[NSMutableDictionary alloc]init];
[dic_data setValue:#[#"1",#"2"] forKey:#"Section"];
[dic_data setValue:#[#"1",#"2",#"3",#"4",#"5",#"6",#"7",#"8"] forKey:#"Section1"];
[dic_data setValue:#[#"1",#"2",#"3",#"4"] forKey:#"Section2"];
for (int i=0; i<dic_data.allKeys.count; i++) {
[arrayForBool addObject:[NSNumber numberWithBool:NO]];
}
}
#pragma mark -
#pragma mark TableView DataSource and Delegate Methods
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if ([[arrayForBool objectAtIndex:section] boolValue]) {
NSString *str =[dic_data.allKeys objectAtIndex:section];
return [[dic_data valueForKey:str]count];
}
else
return 0;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellid=#"hello";
UITableViewCell *cell=[tableView dequeueReusableCellWithIdentifier:cellid];
if (cell==nil) {
cell=[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellid];
}
NSString *str =[dic_data.allKeys objectAtIndex:indexPath.section];
cell.textLabel.text = [[dic_data valueForKey:str]objectAtIndex:indexPath.row];
return cell;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return dic_data.allKeys.count;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
/*************** Close the section, once the data is selected ***********************************/
[arrayForBool replaceObjectAtIndex:indexPath.section withObject:[NSNumber numberWithBool:NO]];
[_expandableTableView reloadSections:[NSIndexSet indexSetWithIndex:indexPath.section] withRowAnimation:UITableViewRowAnimationAutomatic];
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([[arrayForBool objectAtIndex:indexPath.section] boolValue]) {
return 40;
}
return 0;
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
return 40;
}
#pragma mark - Creating View for TableView Section
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
UIView *sectionView=[[UIView alloc]initWithFrame:CGRectMake(0, 0, 280,40)];
sectionView.tag=section;
UILabel *viewLabel=[[UILabel alloc]initWithFrame:CGRectMake(10, 0, _expandableTableView.frame.size.width-10, 40)];
viewLabel.backgroundColor=[UIColor clearColor];
viewLabel.textColor=[UIColor blackColor];
viewLabel.font=[UIFont systemFontOfSize:15];
NSString *str =[dic_data.allKeys objectAtIndex:section];
viewLabel.text=[NSString stringWithFormat:#"List of %#",str];
[sectionView addSubview:viewLabel];
/********** Add UITapGestureRecognizer to SectionView **************/
UITapGestureRecognizer *headerTapped = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(sectionHeaderTapped:)];
[sectionView addGestureRecognizer:headerTapped];
return sectionView;
}
#pragma mark - Table header gesture tapped
- (void)sectionHeaderTapped:(UITapGestureRecognizer *)gestureRecognizer{
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:gestureRecognizer.view.tag];
if (indexPath.row == 0) {
BOOL collapsed = [[arrayForBool objectAtIndex:indexPath.section] boolValue];
for (int i=0; i<dic_data.allKeys.count; i++) {
if (indexPath.section==i) {
[arrayForBool replaceObjectAtIndex:i withObject:[NSNumber numberWithBool:!collapsed]];
}
}
[_expandableTableView reloadSections:[NSIndexSet indexSetWithIndex:gestureRecognizer.view.tag] withRowAnimation:UITableViewRowAnimationTop];
}
}
I have written this code in Objective-C as i don't have much knowledge of swift yet. This is just for the logic. Please convert the code according to your need.

Related

how to create custom cell with label & textfield in objective c

I have taken section with title let say
Monday,Tuesday,Wednesday......Sunday etc.also I have added "+" button after section title & added action on that button
Below is code and screenshot.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return [SectionArray count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
if (section ==0) {
return 0;
}else{
return 3;
}
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
return [SectionArray objectAtIndex:section];
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
CGRect frame = tableView.frame;
UIView *headerView;
if(section == 0 || section == 7) {
}else{
UIButton *addButton=[UIButton buttonWithType:UIButtonTypeContactAdd];
addButton.frame =CGRectMake(frame.size.width-60, 5, 50,30);
addButton.titleLabel.text = #"+";
addButton.tag =section;
// addButton.backgroundColor = [UIColor grayColor];
[addButton addTarget:self action:#selector(AddTimeSlot:) forControlEvents:UIControlEventTouchUpInside];
UILabel *title = [[UILabel alloc] initWithFrame:CGRectMake(30, 10, 100, 30)];
title.text = [SectionArray objectAtIndex:section];
headerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, frame.size.width, frame.size.height)];
[headerView addSubview:title];
[headerView addSubview:addButton];
}
return headerView;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
NSString *cellIdent = #"cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdent];
if(cell == nil){
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdent];
}
return cell;
}
now i have to create cell when i click on "+" button,so please help me .
You can do that as below,
First you have to use an array for the data source that would be manipulated dynamically.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
if (section ==0) {
return 0;
}else{
return numberOfRowNeedToDisplay; //Take global int variable
}
}
In your button action AddTimeSlot:
-(void)addTimeSlot:(id)sender {
//If you want to add cell to section
NSMutableArray *arrayIndexPathsToBeAdded = [[NSMutableArray alloc] init];
for (int i = 0; i < <numberOfCellsYouWantToAdd>; i++) {
[arrayIndexPathsToBeAdded addObject:[NSIndexPath indexPathForRow:i inSection:view.tag]];
}
numberOfRowNeedToDisplay = <numberOfCellsYouWantToAdd>;
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:arrayIndexPathsToBeAdded withRowAnimation:UITableViewRowAnimationRight];
[self.tableView endUpdates];
//If you want to remove cells
NSMutableArray *arrayIndexPathsToBeRemoved = [[NSMutableArray alloc] init];
for (int i = 0; i < <numberOfCellsYouWantToRemove>; i++) {
[arrayIndexPathsToBeRemoved addObject:[NSIndexPath indexPathForRow:i inSection:sectionIndexToBeExpanded]];
}
numberOfRowNeedToDisplay = <numberOfCellsYouWantToRemove>;
[self.tableView beginUpdates];
[self.tableView deleteRowsAtIndexPaths:arrayIndexPathsToBeRemoved withRowAnimation:UITableViewRowAnimationLeft];
}
This is what i have had done:
Please read commented text.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
/** should be change in dynamically
maintain array for every section rows
return row count accordint to section
**/
}
#pragma mark - Private methods
- (void)AddTimeSlot:(UIButton *)sender {
int sectionNo = sender.tag;
// Get you section rowArray(DATASOURCE) and insert you detaisl
// then add UI
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
[tableView beginUpdates];
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationNone];
[tableView endUpdates];
}

Expansion of cell when cell is tapped

I am working with the tableview and working well with that. But i stuck at one place where i want to tap the cell and on the tap the cell should expand its height and and the cell below it should move down according to the expanded height.
The code which i am using is as follows.
- (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];
}
[cell.textLabel setNumberOfLines:0]; // unlimited number of lines
gratitude *grat = [gratitude_list objectAtIndex:indexPath.row];
cell.textLabel.text=grat.gratitude;
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
[cell.textLabel setFont:[UIFont fontWithName:#"Lucida Sans" size:20]];
return cell;
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
gratindex > -1;
if (gratindex == indexPath.row) {
[self collapseSubItemsAtIndex:indexPath];
gratindex = -1;
}
else {
BOOL shouldCollapse = gratindex > -1;
if (shouldCollapse) {
[self collapseSubItemsAtIndex:gratindex];
}
gratindex = (shouldCollapse && indexPath.row > gratindex) ? indexPath.row - [[gratitude_list objectAtIndex:gratitude_list] count] : indexPath.row;
[self expandItemAtIndex:gratitude_list];
}
[self.gratitude_tableview endUpdates];
}
- (void)expandItemAtIndex:(int)index
{
NSMutableArray *indexPaths = [NSMutableArray new];
int insertPos = index + 1;
for (int i = 0; i < [gratitude_list count]; i++) {
[indexPaths addObject:[NSIndexPath indexPathForRow:insertPos++ inSection:0]];
}
[self.gratitude_tableview insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationFade];
[self.gratitude_tableview scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:index inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:YES];
}
- (void)collapseSubItemsAtIndex:(int)index {
NSMutableArray *indexPaths = [NSMutableArray new];
for (int i = index + 1; i <= index + [[gratitude_list objectAtIndex:index] count]; i++) {
[indexPaths addObject:[NSIndexPath indexPathForRow:i inSection:0]];
}
[self.gratitude_tableview deleteRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationFade];
}
Please help me out if i am using wrong approch please help me out.
Please check my answer. If you need any more detailing then you can ask me in comment.
UPDATED:
ANd in .h file
#interface tableViewDemoViewController : UITableViewController
{
NSIndexPath *selectedCellIndexPath;
BOOL clickedAgain;
int heights;
}
And in .m file
#import "tableViewDemoViewController.h"
#interface tableViewDemoViewController ()
#end
#implementation tableViewDemoViewController
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
clickedAgain = YES;
heights =0;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
#warning Potentially incomplete method implementation.
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
#warning Incomplete method implementation.
// Return the number of rows in the section.
return 15;
}
- (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];
}
// Configure the cell...
cell.textLabel.text = [NSString stringWithFormat:#"This is cell %d",indexPath.row ];
return cell;
}
/*
// Override to support conditional editing of the table view.
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
// Return NO if you do not want the specified item to be editable.
return YES;
}
*/
#pragma mark - Table view delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:NO];
if ([selectedCellIndexPath compare:indexPath] == NSOrderedSame && !clickedAgain)
{
heights =40;
clickedAgain =YES;
}
else
{
heights =80;
clickedAgain = NO;
}
[tableView beginUpdates];
selectedCellIndexPath = indexPath;
[tableView endUpdates];
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if(selectedCellIndexPath != nil
&& [selectedCellIndexPath compare:indexPath] == NSOrderedSame )
{
return heights;
}
return 40;
} #end

Using Expandable UITableView to show only one row

I try to do so as in the picture shown in red square.
I need by clicking a user opens a plate and DatePicker. For this I use Expandable UITableView (http://www.wannabegeek.com/?p=338). But I can not understand how to do so in the section could is one element (eg DatePicker) and by clicking on the section shows exactly DatePicker. Now, if at least two elements, then the string is not displayed as a section, but as just a string containing no more elements. And as if I did not change the size of the prototype cell size does not change and I can not see completely DatePicker. Now my DatePicker is behind the text of the cell (which is logical)
Two main questions: how to change the size of the cell, that is normal to see DatePicker and how to make that you can have only one item in the list
In StoryBoard I see:
In simulator:
May have the decision easier?
I use the code:
// Exampl2eController.m
#import "Example2Controller.h"
#implementation Example2Controller
#synthesize dataModel = _dataModel;
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
_dataModel = [NSMutableArray arrayWithObjects:
[NSMutableArray arrayWithObjects:#"Row 1a", #"Row 2a", #"Row 3a", nil],
[NSMutableArray arrayWithObjects:#"Row 1b", #"Row 2b", nil],
[NSMutableArray arrayWithObjects:#"Row 1c", #"Row 2c", #"Row 3c", #"Row 4c", nil],
[NSMutableArray arrayWithObjects:#"Row 1d", nil],
nil];
}
- (NSInteger)numberOfSectionsInTableView:(ExpandableTableView *)tableView
{
// Return the number of sections.
return [_dataModel count];
}
- (NSInteger)tableView:(ExpandableTableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if ([_dataModel count] == 0) {
return 0;
}
// Return the number of rows in the section.
return [[_dataModel objectAtIndex:section] count];
}
- (UITableViewCell *)tableView:(ExpandableTableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"RowCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
cell.textLabel.text = [[_dataModel objectAtIndex:indexPath.section] objectAtIndex:indexPath.row];
// just change the cells background color to indicate group separation
cell.backgroundView = [[UIView alloc] initWithFrame:CGRectZero];
cell.backgroundView.backgroundColor = [UIColor colorWithRed:232.0/255.0 green:243.0/255.0 blue:1.0 alpha:1.0];
return cell;
}
- (UITableViewCell *)tableView:(ExpandableTableView *)tableView cellForGroupInSection:(NSUInteger)section
{
static NSString *CellIdentifier = #"GroupCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
cell.textLabel.text = [NSString stringWithFormat: #"Group %d (%d)", section, [self tableView:tableView numberOfRowsInSection:section]];
// We add a custom accessory view to indicate expanded and colapsed sections
cell.accessoryView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"ExpandableAccessoryView"] highlightedImage:[UIImage imageNamed:#"ExpandableAccessoryView"]];
UIView *accessoryView = cell.accessoryView;
if ([[tableView indexesForExpandedSections] containsIndex:section]) {
accessoryView.transform = CGAffineTransformMakeRotation(M_PI);
} else {
accessoryView.transform = CGAffineTransformMakeRotation(0);
}
return cell;
}
// The next two methods are used to rotate the accessory view indicating whjether the
// group is expanded or now
- (void)tableView:(ExpandableTableView *)tableView willExpandSection:(NSUInteger)section {
UITableViewCell *headerCell = [self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:section]];
[UIView animateWithDuration:0.3f animations:^{
headerCell.accessoryView.transform = CGAffineTransformMakeRotation(M_PI - 0.00001); // we need this little hack to subtract a small amount to make sure we rotate in the correct direction
}];
}
- (void)tableView:(ExpandableTableView *)tableView willContractSection:(NSUInteger)section {
UITableViewCell *headerCell = [self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:section]];
[UIView animateWithDuration:0.3f animations:^{
headerCell.accessoryView.transform = CGAffineTransformMakeRotation(0);
}];
}
// Override to support conditional editing of the table view.
- (BOOL)tableView:(ExpandableTableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
return YES;
}
- (BOOL)tableView:(ExpandableTableView *)tableView canEditSection:(NSInteger)section {
return YES;
}
// Override to support editing the table view.
- (void)tableView:(ExpandableTableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView beginUpdates];
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the row from the data source
[[_dataModel objectAtIndex:indexPath.section] removeObjectAtIndex:indexPath.row];
// cellVisibleForIndexPath: isn't strictly required sicne the table view will determine if the
// the row at that indexPath is actually visible, and do the appropriate manipulation
if ([(ExpandableTableView *)tableView cellVisibleForIndexPath:indexPath]) {
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
if ([[_dataModel objectAtIndex:indexPath.section] count] == 0) {
[_dataModel removeObjectAtIndex:indexPath.section];
[tableView deleteSections:[NSIndexSet indexSetWithIndex:indexPath.section] withRowAnimation:UITableViewRowAnimationFade];
} else {
[tableView reloadSectionCellsAtIndexes:[NSIndexSet indexSetWithIndex:indexPath.section] withRowAnimation:UITableViewRowAnimationFade];
}
}
else if (editingStyle == UITableViewCellEditingStyleInsert) {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
[tableView endUpdates];
}
// Override to support rearranging the table view.
- (void)tableView:(ExpandableTableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath
{
}
// Override to support conditional rearranging of the table view.
- (BOOL)tableView:(ExpandableTableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath
{
// Return NO if you do not want the item to be re-orderable.
return YES;
}
- (BOOL)tableView:(ExpandableTableView *)tableView canRemoveSection:(NSUInteger)section {
return YES;
}
#end
There is an ivar called: _ungroupSingleElement set it to false and then you disable this feature

Assertion Failure when updating uitableview

I get this assertion when trying to update my tableview when clicking on a section header.
* Assertion failure in -[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit_Sim/UIKit-2839.5/UITableView.m:1264
I am just trying to hide and show custom cells whenever I click on a section header view.
code works fine if I replace the update code with reload data. but that's not smooth :(
- (void)noteSectionHeader:(UTNoteSectionHeader *)noteSectionHeader sectionTapped:(NSInteger)section
{
UTNoteItem* noteItem = self.notes[section];
BOOL alreadySelected = noteItem.selected;
if (alreadySelected) {
self.selectedSection = NSNotFound;
[self setSelected:NO forSection:section];
}
else {
self.selectedSection = section;
[self setSelected:YES forSection:section];
}
[self updateSections];
}
- (void)setSelected:(BOOL)selected forSection:(NSInteger)section
{
UTNoteItem* noteItem = self.notes[section];
noteItem.selected = selected;
for (UTNoteItem* tmpItem in self.notes) {
if (tmpItem != noteItem) {
tmpItem.selected = NO;
}
}
}
- (void)updateSections
{
NSMutableArray* deletePaths = [[NSMutableArray alloc] init];
NSMutableArray* addPaths = [[NSMutableArray alloc] init];
for (UTNoteItem* item in self.notes) {
if (item.selected) {
[addPaths addObject:[NSIndexPath indexPathForRow:0 inSection:[self.notes indexOfObject:item]]];
}
else {
[deletePaths addObject:[NSIndexPath indexPathForRow:0 inSection:[self.notes indexOfObject:item]]];
}
}
[self.tableView beginUpdates];
[self.tableView deleteRowsAtIndexPaths:deletePaths withRowAnimation:YES];
[self.tableView insertRowsAtIndexPaths:addPaths withRowAnimation:YES];
[self.tableView endUpdates];
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return self.notes.count;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
UTNoteItem* itemNote = self.notes[section];
if (itemNote.selected) return 1;
return 0;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 40;
}
EDIT:
Here is my new implementation:
-
(void)noteSectionHeader:(UTNoteSectionHeader *)noteSectionHeader sectionTapped:(NSInteger)section
{
/* Check if a section is opened */
if (self.selectedSection != NSNotFound) {
/* A section is open, get the item */
UTNoteItem* theItem = self.notes[self.selectedSection];
/* if the item is the section opened, close it */
if (self.selectedSection == section) {
theItem.selected = NO;
self.selectedSection = NSNotFound;
}
/* The item is not the section, so open it, and close the previous item */
else {
theItem.selected = YES;
UTNoteItem* prevItem = self.notes[self.selectedSection];
prevItem.selected = NO;
self.selectedSection = section;
}
}
/* Nothin is open, just open the section */
else {
self.selectedSection = section;
UTNoteItem* openItem = self.notes[self.selectedSection];
openItem.selected = YES;
}
/* Reload the selected section.. this will not reload the other sections? */
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:section] withRowAnimation:UITableViewRowAnimationAutomatic];
}
I have had a similar problem, however I perform a reload like so:
- (void)noteSectionHeader:(UTNoteSectionHeader *)noteSectionHeader sectionTapped:(NSInteger)section
{
//check our action
if(<will hide section>) {
//hide section
<some action>
} else {
//show section
<some action>
}
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:indexPath.section] withRowAnimation:UITableViewRowAnimationAutomatic];
}
and it reloads again differently with a forced update:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSInteger nRows = 0; //no rows when closed
if(<check section is open>) {
nRows +=<number of data items shown>;
}
return nRows;
}
Where the indexpath.section is the section I wish to hide or show. it is smooth and stable.
Deleting and adding rows is a little dangerous in my opinion, tableviews are very good at doing animated reloads on individual sections or cells.

iOS UITableView reusable cell slow when showing all of them

I have a table view which has 10000+ cells. and there is a segment button (All/Favorite) on the top.
this is the call back for the segment:
- (IBAction)call_segment:(id)sender {
[self.tableView beginUpdates];
[self.tableView reloadData];
[self.tableView endUpdates];
}
for favorite page, even when there are no favorite items, I simply set the cell height to be 0. But in this way, I created all 10000+ cells on screen.
if 'all' is selected, the table works just fine since cells have normal height and only some of them are dequeued on screen.
Here is my code:
//if it's not in favorite, just hide it by setting the height to be 0
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if ([self isFavorite]) {
int uniqueId = [self uniqueIdWithIndexPath:indexPath];
if ([DATABASE isFavoriteWithMode:self.mode uniqueId:uniqueId] == NO) {
return 0;
}
}
return 60;
}
//in table view datasource:
//I think the problem is, when setting the height to be 0, all the cells are allocated. I set the cell to be hidden but still takes memory. any way to deal with it?
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
BOOL isFavorite = [DATABASE isFavoriteWithMode:self.mode uniqueId:[self uniqueIdWithIndexPath:indexPath]];
if ([self isFavorite] && isFavorite == NO) {
cell.hidden = YES;
return [[UITableViewCell alloc] init];
}
else {
cell.hidden = NO;
ListCell *cell = (ListCell *)[tableView dequeueReusableCellWithIdentifier:CELL_LIST];
Datum *datum = [DATABASE datumWithMode:self.mode uniqueId:[self uniqueIdWithIndexPath:indexPath]];
BOOL isRead = [DATABASE isReadWithMode:self.mode uniqueId:[self uniqueIdWithIndexPath:indexPath]];
cell.indexLabel.text = [NSString stringWithFormat:#"%d", datum.uniqueId];
cell.titleLabel.text = [NSString stringWithFormat:#"%#", datum.q];
return cell;
}
}
Note: I dont wanna just show the favorite cells, since the logic is way too complex. I am using sqlite, but i dont think database performance is the problem, since the 'all' tab works just fine.
The reason i wanted to just set the height to be 0 is the simple implementation of cell numbers
- (BOOL)isFavorite {
return self.segment.selectedSegmentIndex == 1;
}
- (IBAction)call_segment:(id)sender {
[self.tableView beginUpdates];
[self.tableView reloadData];
[self.tableView endUpdates];
}
#define NUM_SECTIONS 15
- (int)numRows {
return [DATABASE numberOfDataForModes:self.mode];
}
- (int)numSections {
if ([self numRows] % NUM_SECTIONS > 0) {
int numSections = [self numRows] / [self numRowsPerSection];
if ([self numRows] % [self numRowsPerSection] > 0) {
numSections++;
}
return numSections;
}
return NUM_SECTIONS;
}
- (int)numRowsPerSection {
return [self numRows] / NUM_SECTIONS;
}
- (int)numRowsInLastSection {
if ([self numRows] % ([self numSections] - 1) > 0) {
return [self numRows] % ([self numSections] - 1);
}
else {
return [self numRowsPerSection];
}
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
int start = section * [self numRowsPerSection] + 1;
int end = start + [self numRowsPerSection] - 1;
if (end > [self numRows]) {
end = [self numRows];
}
return [NSString stringWithFormat:#"From %d to %d", start, end];
}
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
NSMutableArray *titles = [NSMutableArray arrayWithCapacity:[self numSections]];
int start = 1;
while (start < [self numRows]) {
NSString *title = [NSString stringWithFormat:#"%d", start];
[titles addObject:title];
start += [self numRowsPerSection];
}
return titles;
}
- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index {
return index;
}
- (int)uniqueIdWithIndexPath:(NSIndexPath *)indexPath {
int uniqueId = indexPath.row + 1 + indexPath.section * [self numRowsPerSection];
return uniqueId;
}
- (NSIndexPath *)indexPathWithUniqueId: (int)uniqueId {
int section = (uniqueId - 1) / [self numRowsPerSection];
int row = uniqueId - 1 - [self numRowsPerSection] * section;
return [NSIndexPath indexPathForRow:row inSection:section];
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if ([self isFavorite]) {
int uniqueId = [self uniqueIdWithIndexPath:indexPath];
if ([DATABASE isFavoriteWithMode:self.mode uniqueId:uniqueId] == NO) {
return 0;
}
}
return 60;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (section == [self numSections] - 1) {
return [self numRowsInLastSection];
}
return [self numRowsPerSection];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return [self numSections];
}
Instead of hiding the cells why dont you just return 0 from the datasource method
– tableView:numberOfRowsInSection:
You can just make use of the isFavorite value within this function and return 0 if there it is NO.
You got it already. The problem is the size of 0 of non-favorite cells. That contradicts the idea of reusabel cells. You will have thousands of cells on the screen, although invisible but existing and therefore resource consuming. Better think of a smarter way of doing that. Your data source delegate (view controller I guess) should only return the number of non-fav cells and therefore cellForRowAtIndexPath should only provide those cells of non-fav items. Plus cellForRowAtIndexPath should actually reuse the cells which I do not see in your code sniplet.
No matter how much you try having 10,000 views onscreen is not going to be the solution to your problem. You need to change your code structure such that you can return 0 for the tableView:numberOfRowsInSection: delegate when the favourites tab is chosen.
Any other 'solution' is an attempt to hack an alternative together, but this will not work and is bad code practice anyway. Implement it properly, by responding to the delegates properly.
I've given up making both table section separated. the logic is way too complicated.
I guess there is no way to save memory even when you hide the cells. Thank you guys for your input. you are all correct.
It's actually not that bad since favorite table are typically not that long. just one section with all entries.

Resources