I would like to number the rows in my app, just like in excel. I use a label for this in the cell. I have written some code which does this perfectly:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
cell.numberlabel.text = [NSString stringWithFormat:#"%li.",(indexPath.row+1)];
return cell;
}
However the problem is, that whenever I delete a row the numbering does not get refreshed. So I thought maybe I would iterate through every cell and set the number again, whenever the user deletes a row. Here is what I did:
NSArray *cells = [tableView visibleCells];
int n = 0;
for (TableViewCell *cell in cells)
{
n++;
cell.numberlabel.text = [NSString stringWithFormat:#"%i.",n];
}
This works great, but again I faced another problem: The tableview is reusing cells so I can not iterate through each and every one of them, only the ones that are actually on screen.
So my question is: How would I go about solving this issue of properly numbering each row in the TableView, even if the user starts deleting rows?
You have to do the following:
Use your first approach by setting the label text in the cellForRowAtIndexPath method
Delete your desired row
Now do not reload the whole tableview but use reloadRowsAtIndexPaths for all visible cells. All offscreen cells will have the correct row number as soon as they become visible because it will be set in the cellForRowAtIndexPath method.
Example:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Default" forIndexPath:indexPath];
cell.textLabel.text = [NSString stringWithFormat:#"%li", indexPath.row +1];
return cell;
}
- (NSArray *) tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewRowAction *deleteAction = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleDefault title:#"Delete" handler:^(UITableViewRowAction *action, NSIndexPath *indexPath) {
self.numberOfRows--;
NSMutableArray *visibleCellIndexPaths = [[tableView indexPathsForVisibleRows] mutableCopy];
[visibleCellIndexPaths filterUsingPredicate:[NSPredicate predicateWithFormat:#"row > %i",indexPath.row]];
[tableView beginUpdates];
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationTop];
[tableView reloadRowsAtIndexPaths:visibleCellIndexPaths withRowAnimation:UITableViewRowAnimationFade];
[tableView endUpdates];
}];
return #[deleteAction];
}
i think first method be great
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
cell.numberlabel.text = [NSString stringWithFormat:#"%li.",(indexPath.row+1)];
return cell;
}
if user delete row, you simply can catch this and reload data and table view redraw with normal values
If you don't want to reload the table after the deletion,
then you can use the following approach:
You can iterate through the cells.
Iteration will start from the indexpath of the cell which recently got deleted to the number of rows.
And you can reduce their number by one during iteration.
Related
I am new to Objective C. I am having the UITableView in that I have to select the tableview cell from top to bottom also without selecting the first cell the second cell won't be selected and the user won't able to select the in-between cells also.can anyone help me how to solve this?
I added the example image here if the user selected the first cell the second cell want to enable and if the user selects the second cell the third cell want to enable remaining cells want to disable.
- (void)viewDidLoad {
[super viewDidLoad];
self.colors = [[NSArray alloc] initWithObjects: #"Red", #"Yellow", #"Green",
#"Blue", #"Purpole", nil];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [self.colors count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"cell";
UITableViewCell *cell = [tableView
dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
cell.textLabel.text = [self.colors objectAtIndex:indexPath.row];
return cell;
}
Your question is not clear, Hope you have an issue with making multiple cell selection:
Allow UITableView to select multiple cells by tableView.allowsMultipleSelection = true;. And then a user can select any number of cells in the table.
Using this way you can able to select the current row and enable next row for selecting. You need to handle another way to disable others row in cellForRowAtIndexPath method.
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
cell.checkMark.selected = YES;
NSString *cellText = cell.textLabel.text;
if(self.colors.count < indexPath.row+1 ){
NSIndexPath *nextIndexPath = [NSIndexPath indexPathForRow:indexPath.row+1 inSection:0];
UITableViewCell *nextCell = [tableView cellForRowAtIndexPath:nextIndexPath];
nextCell.checkMark.userInteractionEnabled = YES;
//OR
nextCell.userInteractionEnabled = YES;
}
}
I have a UITableView in a UIViewController. It's Selection modes are set to Single and Multiple Selection During Editing.
When I set the tableView's editing property to YES, if a process asynchronously runs reloadData, any of my current check marks that have been selected disappear. I implemented my tableView:cellForRowAtIndexPath: method such that it sets the selected property of the cell, based on what the selection was before the reloadData. I've verified those are being set correctly. But to no avail, they still don't show up selected. What do I need to change so that it gets that set correctly? It seems to retain the indented editing mode just fine.
Here's said method:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"ValveCell";
ValveCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
Valve *valve = self.sections[indexPath.section][indexPath.row];
cell.subject = valve;
cell.selected = [self.selections member: valve];
return cell;
}
I use the didSelect and didDeselect methods to keep selections (an NSMutableSet) up to date. E.g.
- (void) tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath {
NSMutableArray *section = self.sections[indexPath.section];
[self.selections removeObject: section[indexPath.row]];
}
Clarification
Given the answers, I think I did not clarify which check marks I was referring to. I am referring to the blue check marks that show a selected state on the left side of the table when the table has had its editing set to YES as shown in this picture:
If a reloadData occurs, it will clear those blue check marks.
Update: My Current Solution
What I did to retain the visual selection status is to modify the KVO method that was firing the reloadData to include code to programmatically reselect the current selections:
...
[self.tableView reloadData];
if (self.tableView.editing) {
[self.sections enumerateObjectsUsingBlock:^(id valves, NSUInteger section, BOOL *stop) {
[(NSArray*)valves enumerateObjectsUsingBlock:^(id valve, NSUInteger row, BOOL *stop) {
if ([self.selections member: valve]) {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow: row inSection: section];
[self.tableView selectRowAtIndexPath: indexPath animated: NO scrollPosition: UITableViewScrollPositionNone];
}
}];
}];
}
...
The selectRowAtIndexPath:animated:scrollPosition: method does the work.
So at this point, I guess my question is, do I really have to do this? Or I'm working too hard?
Setting the cell to "selected" in cellForRowAtIndexPath won't force a call to the UITableView delegate methods so if that's where you're setting your checkmarks, they won't automatically appear upon the table view's reload.
You can either add the checkmarks directly in cellForRowAtIndexPath, ex:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"ValveCell";
ValveCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
Valve *valve = self.sections[indexPath.section][indexPath.row];
cell.subject = valve;
cell.selected = [self.selections member: valve];
if (cell.selected) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
} else {
cell.accessoryType = UITableViewCellAccessoryNone;
}
return cell;
}
Or you can call selectRowAtIndexPath manually, ex:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"ValveCell";
ValveCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
Valve *valve = self.sections[indexPath.section][indexPath.row];
cell.subject = valve;
cell.selected = [self.selections member: valve];
if (cell.selected) {
[self tableView:tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
}
return cell;
}
I made some table views in storyboard and some arrays to display the data.
I am trying to get the tableview cell to go to a certain push depending on what row it is in:
_Locations = #[#"showSignIn",
#"showSignUp",];
For the gestures, I did this:
- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
int row = [indexPath row];
[self performSegueWithIdentifier:_Locations[row] sender:self];
}
But that doesn't seem to work. When I click on any item on the tableview, it always goes to the first one. How do I fix this/ why does it work here, and not here:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
SplashTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"SplashTableViewCell" forIndexPath:indexPath];
// Configure the cell...
int row = [indexPath row];
cell.OptionLabel.text = _Options[row];
return cell;
}
I want a table which, when you click different rows, they gain or lose a checkmark to show they are either selected or not selected.
Currently my tableview will let me select and deselect different rows. But a checkmark will only appear once I have clicked one and then another. The same happens when I deselect, I click a row with a checkmark then click another row and the checkmark disappears.
Here is my code currently:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString * exerciseChoiceSimpleTableIdentifier = #"ExerciseChoiceSimpleTableIdentifier";
UITableViewCell * exerciseChoiceCell = [tableView dequeueReusableCellWithIdentifier:exerciseChoiceSimpleTableIdentifier];
if (exerciseChoiceCell == nil) {
exerciseChoiceCell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:exerciseChoiceSimpleTableIdentifier];
}
//Here we get each exercise we want to display
BExercise * exercise = [[_data getExerciseCompleteList] objectAtIndex:indexPath.row];
//Name the cell after the exercises we want to display
exerciseChoiceCell.textLabel.text = exercise.name;
return exerciseChoiceCell;
}
-(void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath {
if ([tableView cellForRowAtIndexPath:indexPath].accessoryType == UITableViewCellAccessoryNone)
{
// Uncheck the row
[tableView deselectRowAtIndexPath:indexPath animated:YES];
[tableView cellForRowAtIndexPath:indexPath].accessoryType = UITableViewCellAccessoryCheckmark;
}
else
{
// Check the row
[tableView deselectRowAtIndexPath:indexPath animated:YES];
[tableView cellForRowAtIndexPath:indexPath].accessoryType = UITableViewCellAccessoryNone;
}
}
I think the problem is to do with it being selected rather than touch up inside and deselecting when the finger is released from the button.
I am expecting the answer to be quite straight forward but all the similar questions on stackflow haven't dealt with the checkmarks delayed appearance.
It would be great to understand the root of this problem and how to fix it.
change the method
-(void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath
by
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
Note: when I tap the row, then the app crashes.
I'm trying to implement adding a new cell on a user's tap. I found that there was a similar example in WWDC 2011's table view demonstration. Here's my code from my table view.
Here is the error:
2013-03-19 20:04:28.672 Project[51229:c07] *** Assertion failure in -[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit_Sim/UIKit-2380.17/UITableView.m:1070
Here is my code from the table view.
#interface MyPFQueryTableViewController : PFQueryTableViewController <PFLogInViewControllerDelegate, PFSignUpViewControllerDelegate>
#property (nonatomic, strong) NSIndexPath *controlRowIndexPath;
#property (nonatomic, strong) NSIndexPath *tappedIndexPath;
#implementation MyPFQueryTableViewController {
ListItemObject *listDetail;
}
#synthesize controlRowIndexPath;
#synthesize tappedIndexPath;
-(void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
PFObject *object = [self.objects objectAtIndex:indexPath.row];
[object deleteInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
[self loadObjects];
}];
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath object:(PFObject *)object {
static NSString *CellIdentifier = #"listCell";
PFTableViewCell *cell = (PFTableViewCell *)[tableView dequeueReusableCellWithIdentifier:#"listCell"];
if (cell == nil) {
cell = [[PFTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
// Configure the cell
cell.textLabel.text = [object objectForKey:self.textKey];
//cell.imageView.file = [object objectForKey:self.imageKey];
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[super tableView:tableView didSelectRowAtIndexPath:indexPath];
//if user tapped the same row twice let's start getting rid of the control cell
if([indexPath isEqual:self.tappedIndexPath]){
[tableView deselectRowAtIndexPath:indexPath animated:NO];
}
//update the indexpath if needed... I explain this below
indexPath = [self modelIndexPathforIndexPath:indexPath];
//pointer to delete the control cell
NSIndexPath *indexPathToDelete = self.controlRowIndexPath;
//if in fact I tapped the same row twice lets clear our tapping trackers
if([indexPath isEqual:self.tappedIndexPath]){
self.tappedIndexPath = nil;
self.controlRowIndexPath = nil;
}
//otherwise let's update them appropriately
else{
self.tappedIndexPath = indexPath; //the row the user just tapped.
//Now I set the location of where I need to add the dummy cell
self.controlRowIndexPath = [NSIndexPath indexPathForRow:indexPath.row + 1 inSection:indexPath.section];
}
//all logic is done, lets start updating the table
[tableView beginUpdates];
//lets delete the control cell, either the user tapped the same row twice or tapped another row
if(indexPathToDelete){
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPathToDelete]
withRowAnimation:UITableViewRowAnimationNone];
}
//lets add the new control cell in the right place
if(self.controlRowIndexPath){
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:self.controlRowIndexPath]
withRowAnimation:UITableViewRowAnimationNone];
}
//and we are done...
[tableView endUpdates];
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if([indexPath isEqual:self.controlRowIndexPath]){
return 45; //height for control cell
}
return 70; //height for every other cell
}
- (NSIndexPath *)modelIndexPathforIndexPath:(NSIndexPath *)indexPath
{
int whereIsTheControlRow = self.controlRowIndexPath.row;
if(self.controlRowIndexPath != nil && indexPath.row > whereIsTheControlRow)
return [NSIndexPath indexPathForRow:indexPath.row - 1 inSection:0];
return indexPath;
}
#end
The problem is in your didSelectRowAtIndexPath method. You have:
[tableView beginUpdates];
//lets delete the control cell, either the user tapped the same row twice or tapped another row
if(indexPathToDelete){
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPathToDelete]
withRowAnimation:UITableViewRowAnimationNone];
}
//lets add the new control cell in the right place
if(self.controlRowIndexPath){
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:self.controlRowIndexPath]
withRowAnimation:UITableViewRowAnimationNone];
}
//and we are done...
[tableView endUpdates];
Before you make any calls to tell the table to add or remove any rows, you must update your data source with by adding or removing data. The table will check how many sections and rows there are before and after your add/remove rows. The number of sections and rows after the change must properly reflect how much data you add/remove with how many rows you add/remove.
And of course you must implement the numberOfRowsInSection method.
What does your - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section look like?
This error happens when you try to add or delete a row from the UITableView, but the number of rows that you claim to be in the section after the update in that method is not consistent with the new data that should be loaded.
Ex, if your numberOfRowsInSection always returns 4 and you add a row in that section, the tableView will want it to be 5, but it will not be so it will crash. You need to keep track of how many rows are in each section and return that number.