Assertion Failure when updating uitableview - ios

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.

Related

iOS - Expand/Collapse UITableView section with animations

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.

ExpandableTableView: Only 1 row can be expanded at any one time

I am using ExpandableTableView.
Here's a link!
Only 1 row can be expanded at any one time while other should collapsed.
When a row is expanded, any tap on the screen will collapse the expanded row.
How can i get that?
In ExpandableTableView.h
Take previous section variable
NSInteger presection;
In ExpandableTableView.m
in - (id)initWithCoder:(NSCoder *)aDecoder Method set presection intial Value
presection=-1;
Replace didSelectRowAtIndexPath
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// either expand the section or call the delegates method if already expanded
if ([_expandedSectionIndexes containsIndex:indexPath.section]) {
// we're already expanded
[self contractSection:presection]; // Contrace Previos Section
if (indexPath.row == 0) {
// close the section
[self contractSection:indexPath.section];
[super deselectRowAtIndexPath:indexPath animated:YES];
} else {
if ([_expandableDelegate respondsToSelector:#selector(tableView:didSelectRowAtIndexPath:)]) {
[_expandableDelegate tableView:self didSelectRowAtIndexPath:[NSIndexPath indexPathForRow:indexPath.row - 1 inSection:indexPath.section]];
}
}
} else if (indexPath.row == 0 && _ungroupSingleElement && [_expandableDataSource tableView:self numberOfRowsInSection:indexPath.section] == 1) {
if ([_expandableDelegate respondsToSelector:#selector(tableView:didSelectRowAtIndexPath:)]) {
[_expandableDelegate tableView:self didSelectRowAtIndexPath:indexPath];
}
} else {
if (presection!=-1) {
[self contractSection:presection];
}
presection=indexPath.section;
[self expandSection:indexPath.section];
[super deselectRowAtIndexPath:indexPath animated:YES];
}
}

Inconsistent highlighting on selected UITableViewCell

I have a UITableView in which I need to programmatically select a cell if the data model says that the cell represents the selected choice in a list of items. I do this when I'm configuring the UITableViewCell:
if (group == self.theCase.assignedGroup) {
[self.tableView selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionNone];
self.selectedIndexPath = indexPath;
} else {
[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
}
I am seeing very odd behavior with this. If the first row of the tableview is the one that should be selected, the cell doesn't highlight its background properly. However, if the second row is the one that should be selected, it works as it's supposed to (screenshots at the end).
UPDATE: It could have something to do with the fact that I am loading data for the table asynchronously, and while the data is loading I show a different kind of cell with a progress indicator in that first row. Here's the table view data source code that's handling this:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section
{
if (self.hasMorePages) {
return self.groups.count + 1;
}
return self.groups.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell;
if (indexPath.row < self.groups.count) {
cell = [tableView dequeueReusableCellWithIdentifier:#"DSAssignCell" forIndexPath:indexPath];
[self configureCell:cell forRowAtIndexPath:indexPath];
} else {
cell = [tableView dequeueReusableCellWithIdentifier:#"DSLoadingCell" forIndexPath:indexPath];
[self configureLoadingCell:cell forRowAtIndexPath:indexPath];
}
return cell;
}
- (void)configureCell:(UITableViewCell *)cell
forRowAtIndexPath:(NSIndexPath *)indexPath
{
DSGroup *group = self.groups[indexPath.row];
if (group == self.theCase.assignedGroup) {
[self.tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
self.selectedIndexPath = indexPath;
} else {
[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
}
cell.textLabel.text = group.name;
cell.tag = kDataCellTag;
}
- (void)configureLoadingCell:(UITableViewCell *)cell
forRowAtIndexPath:(NSIndexPath *)indexPath
{
UIActivityIndicatorView *activityIndicator;
if ([cell viewWithTag:kActivityIndicatorTag]) {
activityIndicator = (UIActivityIndicatorView *)[cell viewWithTag:kActivityIndicatorTag];
} else {
activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
activityIndicator.center = cell.center;
activityIndicator.tag = kActivityIndicatorTag;
[cell.contentView addSubview:activityIndicator];
}
[activityIndicator startAnimating];
cell.tag = kLoadingCellTag;
}
UPDATE As requested, here is the code that handles the asynchronous loading of the group & agent data from the web service:
- (void)viewDidLoad
{
[super viewDidLoad];
[self resetData];
[self loadData];
}
- (void)resetData
{
self.currentPage = 0;
self.hasMorePages = YES;
self.groups = [[NSMutableArray alloc] initWithCapacity:kGroupsPerPage];
self.agents = [[NSMutableArray alloc] initWithCapacity:kAgentsPerPage];
}
- (void)loadData
{
if (self.showingGroups) {
[DSGroup fetchGroupsOnPage:self.currentPage + 1 perPage:kGroupsPerPage success:^(NSArray *groups, NSDictionary *links, NSNumber *totalEntries) {
[self.groups addObjectsFromArray:groups];
[self didLoadDataPage:(links[#"next"] != [NSNull null])];
} failure:^(NSError *error) {
[self showAlert:#"Could not load groups. Please try again later." withError:error];
}];
} else {
[DSUser fetchUsersOnPage:self.currentPage + 1 perPage:kAgentsPerPage success:^(NSArray *users, NSDictionary *links, NSNumber *totalEntries) {
[self.agents addObjectsFromArray:users];
[self didLoadDataPage:(links[#"next"] != [NSNull null])];
} failure:^(NSError *error) {
[self showAlert:#"Could not load users. Please try again later." withError:error];
}];
}
}
- (void)didLoadDataPage:(BOOL)hasMorePages
{
self.hasMorePages = hasMorePages;
self.currentPage++;
[self.tableView reloadData];
}
Here's a screenshot of trying to select (and highlight) the first row, which is wrong (no gray background):
Here's a screenshot of trying to select (and highlight) the second row, which is correct:
Any idea what might be going on here?
I wasn't able to fix this using the built-in selection styles of UITableViewCell, but subclassing it and overriding setSelected:animated fixed it:
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
[super setSelected:selected animated:animated];
self.contentView.backgroundColor = selected ? [UIColor grayColor] : [UIColor whiteColor];
}

UITableView with open & closed sections

I have a UITableView with several sections.
I would like click on a section to "close/open" its content, in order to show/hide the rows under it. So that, I can keep some sections open (with its rows visible) and others close, with the next section immediately below the previous section header.
How can I implement it? Do I need to subclass the UITableView and add a gesture recognizer and somehow add an animation to the rows? But I'm not sure this is easy...
thanks
Use reloadSections:withRowAnimation to trigger the change;
Provide the updated number of rows in UITableViewDataSource delegate
I can't comment for the question as I don't have enough reputation.
As per my understanding, you want to achieve accordion functionality for your table view. For this, please check this:- effect or animation in UItableVIew and How to implement an accordion view for an iPhone SDK app?
Just subclass sectionHeaderView and define delegate methods like this.
#protocol SectionHeaderViewDelegate <NSObject>
#optional
-(void)sectionHeaderView:(SectionHeaderView*)sectionHeaderView sectionOpened:(NSInteger)section;
-(void)sectionHeaderView:(SectionHeaderView*)sectionHeaderView sectionClosed:(NSInteger)section;
#end
And then in tableViewCOntroller.h
#interface TableViewController : UITableViewController <SectionHeaderViewDelegate>
And in tableViewCOntroller.m
-(void)sectionHeaderView:(SectionHeaderView*)sectionHeaderView sectionOpened:(NSInteger)sectionOpened
{
SectionInfo *sectionInfo = [self.sectionInfoArray objectAtIndex:sectionOpened];
sectionInfo.open = YES;
/*
Create an array containing the index paths of the rows to insert: These correspond to the rows for each quotation in the current section.
*/
NSInteger countOfRowsToInsert = [sectionInfo.play.quotations count];
NSMutableArray *indexPathsToInsert = [[NSMutableArray alloc] init];
for (NSInteger i = 0; i < countOfRowsToInsert; i++) {
[indexPathsToInsert addObject:[NSIndexPath indexPathForRow:i inSection:sectionOpened]];
}
/*
Create an array containing the index paths of the rows to delete: These correspond to the rows for each quotation in the previously-open section, if there was one.
*/
NSMutableArray *indexPathsToDelete = [[NSMutableArray alloc] init];
NSInteger previousOpenSectionIndex = self.openSectionIndex;
if (previousOpenSectionIndex != NSNotFound) {
SectionInfo *previousOpenSection = [self.sectionInfoArray objectAtIndex:previousOpenSectionIndex];
previousOpenSection.open = NO;
[previousOpenSection.headerView toggleOpenWithUserAction:NO];
NSInteger countOfRowsToDelete = [previousOpenSection.play.quotations count];
for (NSInteger i = 0; i < countOfRowsToDelete; i++) {
[indexPathsToDelete addObject:[NSIndexPath indexPathForRow:i inSection:previousOpenSectionIndex]];
}
}
// Style the animation so that there's a smooth flow in either direction.
UITableViewRowAnimation insertAnimation;
UITableViewRowAnimation deleteAnimation;
if (previousOpenSectionIndex == NSNotFound || sectionOpened < previousOpenSectionIndex) {
insertAnimation = UITableViewRowAnimationTop;
deleteAnimation = UITableViewRowAnimationBottom;
}
else {
insertAnimation = UITableViewRowAnimationBottom;
deleteAnimation = UITableViewRowAnimationTop;
}
// Apply the updates.
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:indexPathsToInsert withRowAnimation:insertAnimation];
[self.tableView deleteRowsAtIndexPaths:indexPathsToDelete withRowAnimation:deleteAnimation];
[self.tableView endUpdates];
self.openSectionIndex = sectionOpened;
}
-(void)sectionHeaderView:(SectionHeaderView*)sectionHeaderView sectionClosed:(NSInteger)sectionClosed
{
/*
Create an array of the index paths of the rows in the section that was closed, then delete those rows from the table view.
*/
SectionInfo *sectionInfo = [self.sectionInfoArray objectAtIndex:sectionClosed];
sectionInfo.open = NO;
NSInteger countOfRowsToDelete = [self.tableView numberOfRowsInSection:sectionClosed];
if (countOfRowsToDelete > 0)
{
NSMutableArray *indexPathsToDelete = [[NSMutableArray alloc] init];
for (NSInteger i = 0; i < countOfRowsToDelete; i++) {
[indexPathsToDelete addObject:[NSIndexPath indexPathForRow:i inSection:sectionClosed]];
}
[self.tableView deleteRowsAtIndexPaths:indexPathsToDelete withRowAnimation:UITableViewRowAnimationTop];
}
self.openSectionIndex = NSNotFound;
if ([selectedIndexes count]>0)
{
for(NSIndexPath *indexPath in selectedIndexes)
{
if(sectionClosed == indexPath.section)
{
[sectionInfo.headerView changeOnHighlighted:YES];
break;
}
else
{
[sectionInfo.headerView changeOnHighlighted:NO];
}
}
}
else
{
[sectionInfo.headerView changeOnHighlighted:NO];
}
}
And for further details you can refer this sample project from iOS dev lib..
Here is a simple solution by which you can even create a custom expanded/ collapse view.
here are simple step
1) create a custom view add button over it.
///
join all outlet and create on BOOL variable in view class
#property (weak, nonatomic) IBOutlet UIButton *BtnAction;
#property(assign, nonatomic)BOOL isOpen;
// Create a header where tableview is added and you want it.
here is a simple logic to added as much as you need . I have added which are in headertitle array I wanted it to be dynamic .
NSMutableArray * headerTitle = [NSMutableArray arrayWithObjects:#"Your Order", #"Delivery Address", #"Pay By", nil];
for (NSUInteger index = 0; index<headerTitle.count; index++) {
VGOrderHeader* HeaderView = [[[NSBundle mainBundle] loadNibNamed:#"VGOrderHeader" owner:self options:nil] lastObject];
HeaderView.frame = CGRectMake(0, 0, 32, 40);
HeaderView.BtnAction.tag = index;
if (index == 0) {
HeaderView.isOpen = YES;
HeaderView.lblPlus.text = [NSString stringWithFormat:#"open"];
}
[HeaderView.BtnAction addTarget:self action:#selector(selectSectionToOpen:) forControlEvents:UIControlEventTouchUpInside];
[headerArray addObject:HeaderView];
}
/// Here is header click action.
-(void)selectSectionToOpen:(UIButton *)sender{
for (NSUInteger Increment=0; Increment<headerArray.count; Increment++) {
if (sender.tag == Increment) {
DCOrderHeader* HeaderView= headerArray[Increment];
HeaderView.isOpen = !HeaderView.isOpen;
}
}
// little animation
dispatch_async(dispatch_get_main_queue(), ^{
[UIView transitionWithView:self.tableView
duration:0.55f
options:UIViewAnimationOptionTransitionCrossDissolve
animations:^(void) {
[self.tableView reloadData];
} completion:NULL];
});
}
/// Finally Assign view in header method of table view and provide a height
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
return 40;
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
return [headerArray objectAtIndex:section];
}
// Final Touch
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return headerArray.count;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
DCOrderHeader* HeaderView = headerArray[section];
if (HeaderView.isOpen == YES) {
return self.someArray.count;
}else{
return 0;
}
}

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