I have a tableView with multiple sections.
Now I want to reload all sections of tableView except the first one (because it's static and doesn't change).
I now I can reload sections by this method:
reloadSections(IndexSet, with: UITableView.RowAnimation)
but it requires the number of sections and in my app this is dynamic and changes in time.
I can only say reload whatever exist after first section.
I thought I should define a IndexSet somehow like this:
NSIndexSet *indexSet = [NSIndexPath indexPathWithIndexes:(NSUInteger[]){1, ... } length:???];
and I don't know how can I do that!
Any ideas or suggestion would be greatly appreciated.
update
This is the numberOfSectionsInTableView implementation:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
// Inbox Segment
if (self.selectedIndex == 0) {
if (self.inboxArray.count > 0) {
return self.inboxArray.count + 2;
}
}
// Sent Segment
else if (self.selectedIndex == 1) {
if (self.sentArray.count > 0) {
return self.sentArray.count + 2;
}
}
// Outbox Segment
else if (self.selectedIndex == 2) {
if (self.outboxArray.count > 0) {
return self.outboxArray.count + 2;
}
}
// Spam Segment
else if (self.selectedIndex == 3) {
if (self.spamArray.count > 0) {
return self.spamArray.count + 2;
}
}
// Trash Segment
else if (self.selectedIndex == 4) {
if (self.trashArray.count > 0) {
return self.trashArray.count + 2;
}
}
return 1;
}
Assuming you know the amount of sections:
NSIndexSet *sectionsIndexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1, self.sections.count - 1)]
[self.tableView reloadSections:sectionsIndexSet withRowAnimation:UITableViewRowAnimationNone];
Swift:
let sectionsIndexSet = IndexSet(1..<sections.count)
tableView.reloadSections(sectionsIndexSet, with: .none)
Related
Hello I have implemented example Expand Table
-(void)sectionButtonTouchUpInside:(UIButton*)sender
{
// sender.backgroundColor = [UIColor greenColor];
[self.tableView beginUpdates];
int section = sender.tag;
bool shouldCollapse = ![collapsedSections containsObject:#(section)];
if (shouldCollapse)
{
int numOfRows = [self.tableView numberOfRowsInSection:section];
NSArray* indexPaths = [self indexPathsForSection:section withNumberOfRows:numOfRows];
[self.tableView deleteRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationTop];
[collapsedSections addObject:#(section)];
}
else
{
int numOfRows = 0 ;
switch (section)
{
case 0:
numOfRows = (int)aArray.count;
break;
case 1:
numOfRows = (int)bArray.count;
break;
case 2:
numOfRows = (int)cArray.count;
break;
case 3:
numOfRows = (int)dArray.count;
break;
}
NSArray* indexPaths = [self indexPathsForSection:section withNumberOfRows:numOfRows];
[self.tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationTop];
[collapsedSections removeObject:#(section)];
}
[self.tableView endUpdates];
}
My program crashed in above code with this message:
Terminating app due to uncaught exception
'NSInternalInconsistencyException', reason: 'Invalid update: invalid
number of rows in section 2. The number of rows contained in an
existing section after the update (1) must be equal to the number of
rows contained in that section before the update (1), plus or minus
the number of rows inserted or deleted from that section (0 inserted,
1 deleted) and plus or minus the number of rows moved into or out of
that section (0 moved in, 0 moved out).
Update
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if(![_collapsedSections containsObject:#(section)])
{
return 0;
}
else
{
int count = 0;
switch (section)
{
case 0:
count = (int)aArray.count;
break;
case 1:
count = (int)bArray.count;
break;
case 2:
count = (int)cArray.count;
break;
case 3:
count = (int)dArray.count;
break;
}
return count;
}
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if(section == 0)
{
return [__collapse containsObject:#(section)] ? 0 : [self.section1 count];
}
else if(section == 1)
{
return [__collapse containsObject:#(section)] ? 0 : [self.section2 count];
}
else if(section == 2)
{
return [__collapse containsObject:#(section)] ? 0 : [self.section3 count];
}
else
{
return 0;
}
}
Modify this method like above..
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
int count = 0;
switch (section)
{
case 0:
count = (int)aArray.count;
break;
case 1:
count = (int)bArray.count;
break;
case 2:
count = (int)cArray.count;
break;
case 3:
count = (int)dArray.count;
break;
}
return count;
}
Try this..
For Ur Code..Initially ur rows are 0. So, After u click event methods try to insert ur array no.of.rows.. Thats causing error.
I'm creating a recipes app and have run into this semantic issue in the number of rows in section method of my table view. This is the first time I've really worked with table Views and I'm wondering if someone could perhaps see what I've done wrong and point me in the right direction. Thanks!
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
if (menuInt == 0)
return [soupsArray count];
if (menuInt == 1)
return [saladsArray count];
if (menuInt == 2)
return [appetizersArray count];
if (menuInt == 3)
return [entreeArray count];
if (menuInt == 4)
return [dissertsArray count];
[self.tableView reloadData];
}
What happens if all the if conditions fail? You need to make sure that you at least return an NSInteger even though you are sure one of the if conditions will succeed for sure. That's just how it is.
Also, as Martin R pointed out, you should not have reloadData in the function.
try one of these 2 solutions (the reload data must occur before the return statement also)
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
int counter = 0;
// Return the number of rows in the section.
if (menuInt == 0)
counter = [soupsArray count];
if (menuInt == 1)
counter = [saladsArray count];
if (menuInt == 2)
counter = [appetizersArray count];
if (menuInt == 3)
counter = [entreeArray count];
if (menuInt == 4)
counter = [dissertsArray count];
[self.tableView reloadData];
return counter;
}
or
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
if (menuInt == 0) {
[self.tableView reloadData];
return [soupsArray count];
}
else if (menuInt == 1) {
[self.tableView reloadData];
return [saladsArray count];
}
else if (menuInt == 2) {
[self.tableView reloadData];
return [appetizersArray count];
}
else if (menuInt == 3) {
[self.tableView reloadData];
return [entreeArray count];
}
else if (menuInt == 4) {
[self.tableView reloadData];
return [dissertsArray count];
else {
[self.tableview reloadData];
return 0;
}
}
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.
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.
I am looping through all the cells in my table. Each cell contains a UITextField. I want to validate the data in the text field, but I only get a valid string from the first row of the table. All other subsequent rows return null for its text. Any ideas?
-(IBAction)saveContactToDB:(UIBarButtonItem *)sender
{
//TODO Loop through each row and test the data.
// At least one name has to be entered. If this fails a UIAlert is prompted; otherwise, write to DB
NSLog(#"Attempting to save contact");
NSInteger contactID;
for (NSInteger section = 0; section < [_contactInfoTable numberOfSections]; ++section)
{
for (NSInteger row = 0; row < [_contactInfoTable numberOfRowsInSection:section]; ++row)
{
ContactFieldCell *cell = (ContactFieldCell *)[_contactInfoTable cellForRowAtIndexPath:[NSIndexPath indexPathForRow:section
inSection:row]];
NSLog(#"%#", cell.cellTextField.text);
NSLog(#"%#", cell.cellTextField.placeholder);
//Make sure at least one name is entered
//When i = 0 && j = 0 it means we are on the first row of the name section
if (section == 0 && row ==0 && cell.cellTextField.text == #"")
{
NSLog(#"Name is required");
//Present alert
goto ALERTSHOWN;
}
else
{
//Write the data to the DB in its respective place
if (section == 0)
{
//Names
contactID = [APP.localDatabase saveContactName:cell.cellTextField.text];
}
else if (section == 1)
{
//Position
[APP.localDatabase saveContactInfoValue:cell.cellTextField.text
WithType:POSITION
ForContact:contactID
AtOrder:row];
}
else if (section == 2)
{
//Schedule
[APP.localDatabase saveContactInfoValue:cell.cellTextField.text
WithType:SCHEDULE
ForContact:contactID
AtOrder:row];
}
else if (section == 3)
{
//Phone number
[APP.localDatabase saveContactInfoValue:cell.cellTextField.text
WithType:PHONE
ForContact:contactID
AtOrder:row];
}
else if (section == 4)
{
//Miscellaneous
[APP.localDatabase saveContactInfoValue:cell.cellTextField.text
WithType:MISCELLANEOUS
ForContact:contactID
AtOrder:row];
}
}
}
}
[self.view removeFromSuperview];
ALERTSHOWN:;
}
On getting the cell through the method cellForRowAtIndexPath you transmit as row the variable section and as section the variable row. It should be:
ContactFieldCell *cell = (ContactFieldCell *)[_contactInfoTable cellForRowAtIndexPath:[NSIndexPath indexPathForRow:row inSection:section]];