How to prevent overflow of adjustable height tableview cell on delete - ios

In a UITableView Controller, I have just added 'swipe to delete' by implementing tableView: commitEditingStyle: forRowAtIndexPath. Additionally, the rows can be selected to expand showing more content.
The undesired result after swiping:
The two lower rows remain in view after swiping until about about 0.5 seconds after the undelete animation completes.
A screenshot of IB:
The cell's contents have grown into the lower cell without it showing that it has been selected. (Selection causes the cell to increase height and give it a grayish background color.) This is occurring on every row in 2 similarly operating view controllers.
I have tried (without success) to intercept the 'selection' in several UITableViewDelegate methods, and cannot find out how to stop this from occurring. I have also tried setting the IB dynamic prototype cells to height: 85.
Looking for ideas on how to prevent this expansion from occurring.
EDIT
- (void)viewDidLoad {
....
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = kCellHeight;
....
}
#pragma mark - TableView delegate
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return [[self.fetchedResultsController sections] count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
id <NSFetchedResultsSectionInfo> sectionInfo = [self.fetchedResultsController sections][section];
NSInteger *rows = (NSInteger *)[sectionInfo numberOfObjects];
if (!self.rowsInSection)
self.rowsInSection = rows;
if (rows > 0)
return [sectionInfo numberOfObjects];
else {
[tableView setSeparatorColor:[UIColor clearColor]];
[tableView setBounces:NO];
return 1;
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *identifier = self.rowsInSection > 0 ? #"numberIdentifier" : #"noNumbersIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier forIndexPath:indexPath];
if (self.rowsInSection > 0)
[self configureCell:cell atIndexPath:indexPath];
else
cell.selectionStyle = UITableViewCellSelectionStyleNone;
// [self.arrayOfIndexPaths addObject:indexPath];
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
NSIndexPath *selectedIndexPath = [self.tableView indexPathForSelectedRow];
if (selectedIndexPath) {
if (tableView.editing)
return 85.0;
else if (selectedIndexPath.row == indexPath.row)
return 185.0;
}
return 85.0;
}
- (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)path {
if (tableView.editing)
return nil;
// If real rows exist, return the path, making row selectable
if (self.rowsInSection > 0)
return path;
// Otherwise do not allow the row to be selected
return nil;
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
id <NSFetchedResultsSectionInfo> sectionInfo = [self.fetchedResultsController sections][0];
if ([sectionInfo numberOfObjects] > 0)
// Return the contentView to stop the header from sliding with delete
return [tableView dequeueReusableCellWithIdentifier:#"numberHeaderIdentifier"].contentView;
else
return [tableView dequeueReusableCellWithIdentifier:#"emptyHeaderIdentifier"];
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
return 75;
}
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
Number *aNumber = [self.fetchedResultsController objectAtIndexPath:indexPath];
[cell configureSubviewsInCell:cell withNumber:aNumber];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView beginUpdates];
[tableView endUpdates];
}
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
[context deleteObject:[self.fetchedResultsController objectAtIndexPath:indexPath]];
}
}

You should set the hidden property of the labels that you don't want to show when the table view cell is not selected. For example:
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
Number *aNumber = [self.fetchedResultsController objectAtIndexPath:indexPath];
UILabel *label1 = (UILabel *)[cell.contentView viewWithTag:501];
label1.text = [aNumber valueForKey:#"number"];
if (!cell.selected)
{
label1.hidden = YES;
}
else
{
label1.hidden = NO;
}
.....
}
Then in didSelectRowAtIndexPath:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView beginUpdates];
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
UILabel *label1 = (UILabel *)[cell.contentView viewWithTag:501];
label1.text = [aNumber valueForKey:#"number"];
label1.hidden = NO;
[tableView endUpdates];
}
You should look into subclassing UITableViewCell so you don't have to use tags to access subviews.

The 'hiding' solution posted by beowulf is a valid option. In addition and because the swipe (to begin editing) was causing subviews in the cell to become 'unclipped', a method needed to be overrided, like so:
// this method is called as the swipe to delete is started
- (void)tableView:(UITableView *)tableView willBeginEditingRowAtIndexPath:(nonnull NSIndexPath *)indexPath
{
// only hide for unexpanded (unselected) cells
if ([self.selectedRowIndex compare:indexPath] != NSOrderedSame)
{
NumberTableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
// a cell subclass method to hide/unhide subviews that fall into
// the next cell below
[cell subViewsInCellShouldBeHidden:YES];
}
}

Related

UITableView on scrolling goes wrong

I have created UITableView on UIView Class.
Based on the selection row setting the alpha value of UIImageView to 1.0f
on deselect the row setting alpha value to 0.2f, which work good.
But on scrolling the selected value (i.e alpha 1.0f) is highlighted with wrong cell, which was not selected at all.
Please find the below code which i have implemented.
Your feedback will be appreciated.
// Code
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [colorNameList count]; // count is century.
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
return [self loadMoreTableViewCellForTableView:tableView indexPath:indexPath];
}
- (FilterColorTableViewCell *)loadMoreTableViewCellForTableView:(UITableView *)tableView indexPath:(NSIndexPath *)indexPath {
FilterColorTableViewCell *cell = (FilterColorTableViewCell *)[tableView dequeueReusableCellWithIdentifier:#"FilterColorTableViewCell"];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
[cell.filterColorImageView setBackgroundColor:[UIColor colorWithHexString:[[[ColorModelClass colorListNames]allValues] objectAtIndex:indexPath.row]alpha:1]];
cell.lbl_FilterColorName.text = [colorNameList objectAtIndex:indexPath.row];
cell.lbl_FilterColorCount.text = [NSString stringWithFormat:#"Items: %ld",(long)indexPath.row];
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;
{
FilterColorTableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
self.filterColorTableView.allowsMultipleSelection = YES;
cell.filterSelectionColor.alpha = (cell.selected ?1.0f:0.2f);
}
-(void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath{
FilterColorTableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
cell.filterSelectionColor.alpha = (cell.selected ?1.0f:0.2f);
}
-(CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
return UITableViewAutomaticDimension;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
return UITableViewAutomaticDimension;
}
You should also use this line of code:
cell.filterSelectionColor.alpha = (cell.selected ?1.0f:0.2f);
in your
- (FilterColorTableViewCell *)loadMoreTableViewCellForTableView:(UITableView *)tableView indexPath:(NSIndexPath *)indexPath {
because it reuses the cell and this is why your previous settings are not reset.
If I understood you correctly, cell.filterSelectionColor.alpha is not correct as soon as you scroll through the TableView, isn't it?
You are relying on the Cell selected property, though cell position may not be the same as when they were created when scrolling. You should store the selected cells somewhere else (an array or so) and refresh Cell's status in cellForRowAtIndexPath. Something like:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
return [self loadMoreTableViewCellForTableView:tableView indexPath:indexPath];
}
- (FilterColorTableViewCell *)loadMoreTableViewCellForTableView:(UITableView *)tableView indexPath:(NSIndexPath *)indexPath {
FilterColorTableViewCell *cell = (FilterColorTableViewCell *)[tableView dequeueReusableCellWithIdentifier:#"FilterColorTableViewCell"];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
[cell.filterColorImageView setBackgroundColor:[UIColor colorWithHexString:[[[ColorModelClass colorListNames]allValues] objectAtIndex:indexPath.row]alpha:1]];
cell.lbl_FilterColorName.text = [colorNameList objectAtIndex:indexPath.row];
cell.lbl_FilterColorCount.text = [NSString stringWithFormat:#"Items: %ld",(long)indexPath.row];
// selectedItems is an array of booleans with as many elements as the TableView
cell.filterSelectionColor.alpha = self.selectedItems[indexPath.row] ? 1.0f : 0.2f;
return cell;
}
When tableView reloads it uses the same cell instance and changes the data. When you reload/scroll your cellForRowAtIndexpath method is called and over there you will have to specify which cell should have which alpha. Below change in your code is required :
- (FilterColorTableViewCell *)loadMoreTableViewCellForTableView:(UITableView *)tableView indexPath:(NSIndexPath *)indexPath {
FilterColorTableViewCell *cell = (FilterColorTableViewCell *)[tableView dequeueReusableCellWithIdentifier:#"FilterColorTableViewCell"];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
[cell.filterColorImageView setBackgroundColor:[UIColor colorWithHexString:[[[ColorModelClass colorListNames]allValues] objectAtIndex:indexPath.row]alpha:1]];
cell.lbl_FilterColorName.text = [colorNameList objectAtIndex:indexPath.row];
cell.lbl_FilterColorCount.text = [NSString stringWithFormat:#"Items: %ld",(long)indexPath.row];
// Set alpha here
cell.filterSelectionColor.alpha = (cell.selected ?1.0f:0.2f);
return cell;
}
create an int property in your controller
NSMutableArray *selectedCells;
initialize the variable..
- (void)viewDidload {
...
...
selectedCells = [NSMutableArray array];
}
set the value on selection and deselection..
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;
{
...
...
[selectedCells addObject:[NSNumber numberWithInt:indexPath.row]];
}
-(void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath{
...
...
for (int i=0;i<selectedCells.count;i++) {
if([selectedCells[i] intValue] == indexPath.row)
[selectedCells removeObjectAtIndex:i];
}
}
As the cells are reused by the tableView.. you need to setup the cell from the cellForRow method..
- (FilterColorTableViewCell *)loadMoreTableViewCellForTableView:(UITableView *)tableView indexPath:(NSIndexPath *)indexPath {
FilterColorTableViewCell *cell = (FilterColorTableViewCell *)[tableView dequeueReusableCellWithIdentifier:#"FilterColorTableViewCell"];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
...
...
BOOL selected = [selectedCells containsObject:[NSNumber numberWithInt:indexPath.row]];
cell.filterSelectionColor.alpha = (selected) ?1.0f:0.2f;
return cell;
}

How to reload custom table view cell after last row deletion?

I have a UITableView with a simple array as data source. I designed a custom nib for a special UITableViewCell that is shown when the array is empty (i.e. when the app first starts).
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if ([self.wallet count] == 0)
return 1;
else return [self.wallet count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// If there isn't any coupon yet, then show the custom "No Coupon Yet" cell
if ([self.wallet count] == 0)
return [tableView dequeueReusableCellWithIdentifier:#"NoCouponCell" forIndexPath:indexPath];
else {
CouponCell *cell = [tableView dequeueReusableCellWithIdentifier:#"CouponCell" forIndexPath:indexPath];
Coupon *coupon = [self.wallet objectAtIndex:indexPath.row];
[cell configureForCoupon:coupon];
return cell;
}
}
Since I'd like to add the swipe-to-delete functionality, I provided the following method for the view controller.
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
[self.wallet removeObjectAtIndex:indexPath.row];
// Creates a new, temporary array holding just the one index-path item
NSMutableArray *tmp = [[NSMutableArray alloc] initWithObjects:indexPath, nil];
// Tells the table view to delete the row with a nice animation
[tableView deleteRowsAtIndexPaths:tmp withRowAnimation:UITableViewRowAnimationAutomatic];
}
However, when the table view has only one row and I try to delete it, my app crashes. Why?
EDIT: The debug info tells that a NSInternalInconsistencyException is raised.
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete)
{
// Delete the row from the data source
[self.wallet removeObjectAtIndex:indexPath.row];
[tableView reloadData]; // reload your table to see updates
// or if you what some animation use|
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObjects:indexPath]
withRowAnimation:UITableViewRowAnimationAutomatic];
...
Here's a fix for your cellForRowAtIndexPath:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
CouponCell *cell = [tableView dequeueReusableCellWithIdentifier:[self.wallet count] == 0 ? #"NoCouponCell" : #"CouponCell" forIndexPath:indexPath];
if ([self.wallet count] > 0)
{
Coupon *coupon = [self.wallet objectAtIndex:indexPath.row];
[cell configureForCoupon:coupon];
}
return cell;
}
try this in your code:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [self.wallet count];
}
You are returning a rowcount of 1 if your data array wallet has no elements:
if ([self.wallet count] == 0)
return 1;
else return [self.wallet count];
But in the commitEditingStyle method you don't handle this case: This means that it's possible to delete the NoCouponCell. And I assume that the crash occurs, because you want to remove an object from your data-array which does not exist.
[self.wallet removeObjectAtIndex:indexPath.row];
Solution: Use the following delegate method to determine when a cell can be edited: This prevents the delegate method commitEditingStyle to be called.
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath{
return [self.wallet count] > 0
}
Try this piece of code:
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
[self.wallet removeObjectAtIndex:indexPath.row];
// Creates a new, temporary array holding just the one index-path item
NSMutableArray *tmp = [[NSMutableArray alloc] initWithObjects:indexPath, nil];
// Tells the table view to delete the row with a nice animation
[tableView beginUpdates];
[tableView deleteRowsAtIndexPaths:tmp withRowAnimation:UITableViewRowAnimationAutomatic];
[tableView endUpdates];
}
Use this in your numberOfRowsInSection function:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [self.wallet count];
}
And at the end of your commitEditingStyle function, use
[tableView reloadData];
you can add a view to the UITableview tableFooterView on tableview datasource, you can put your custome cell'content in this view
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
if (dataArray.count == 0) {
UIView *view = [[UITableView alloc] initWithFrame:self.view.bounds];
view.backgroundColor = [UIColor blueColor];
self.tableView.tableFooterView = view;
}
return dataArray.count;
}

Expandable rows in UITableView is set to be opened

I have managed to make the TableView expandable. The problem is that when I start the app, it's always opened. I want it to be closed, and to be opened just when I hit the row.
What am I missing here? How can I set it to be open at start?
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 3;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 5;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
if (!indexPath.row)
{
// first row
cell.textLabel.text = #"Expandable"; // only top row showing
}
else
{
cell.textLabel.text = #"Some Detail";
cell.accessoryView = nil;
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
return cell;
}
- (BOOL)tableView:(UITableView *)tableView canCollapseSection:(NSInteger)section
{
return YES;
}
You first create a regular table with some array of booleans that holds the state of each row (open/close).
Then, when you hit a first row in a section, I reload the table with new rows:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
//subject hitted
if (indexPath.row == 0)
{
// set relevant boolean here ,also , reload the table again with the new rows
collapsedRows[indexPath.section]= ! collapsedRows[indexPath.section];
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:indexPath.section]withRowAnimation:UITableViewRowAnimationFade];
}
//sub subject hitted
else
{
}
}

Being able to tap on ONLY one item in a UITableView

So I have a subclassed UITableView that lists data. I need to make only one cell selectable.
- (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// rows in section 0 should not be selectable
UITableViewCell *cell = (UITableViewCell *)[tableView cellForRowAtIndexPath:indexPath];
if (indexPath.row == 3) {
cell.userInteractionEnabled = YES;
cell.selectionStyle = UITableViewCellSelectionStyleGray;
NSLog(#"Hey whats up, address");
return indexPath;
}
else {
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
return nil;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
My code that does this so far works, but only after the cell is clicked at least once. Placing it in didSelectCell allows the cell to be selected if held down for 1-2 seconds.
You should do that in the cellForRowAtIndexPath. Like this:
-(UITableViewCell*)tableView:(UITableView *)aTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
// your code
if(indexPath.row != 3)
{
[cell setSelectionStyle:UITableViewCellSelectionStyleNone];
{
}
And then on didSelectRowAtIndexPath:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
if(indexPath.row == 3)
{
//do your stuff
{
}

UITableView reloadRowsAtIndexPaths graphical glitch

If I call reloadRowsAtIndexPaths for the first cell of a section, with previous section empty and the one above not-empty, I get a strange animation glitch (even if I specify "UITableViewRowAnimationNone") where the reloaded cell slides down from the above section..
I tried to simplify the example as much as possible:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 3;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (section == 0)
return 1;
else if (section == 1)
return 0;
else if (section == 2)
return 3;
return 0;
}
- (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];
}
// Configure the cell...
cell.textLabel.text = #"Text";
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSArray *editedCell = [[NSArray alloc] initWithObjects:indexPath, nil];
//[self.tableView beginUpdates];
[self.tableView reloadRowsAtIndexPaths:editedCell withRowAnimation:UITableViewRowAnimationNone];
//[self.tableView endUpdates];
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
return #"Section";
}
Actually you can comment out the last method, but it gives a better understanding of the problem.
You can set values you want to the cell directly, not letting table to reload itself (and thus avoiding any unwanted animations). Also to make code clearer and avoid code duplication lets move cell setup to a separate method (so we'll be able to call it from different locations):
- (void) setupCell:(UITableViewCell*)cell forIndexPath:(NSIndexPath*)indexPath {
cell.textLabel.text = #"Text"; // Or any value depending on index path
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
[self setupCell:cell forIndexPath:indexPath];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// create cell
// Configure the cell...
[self setupCell:cell forIndexPath:indexPath];
return cell;
}

Resources