My table is relatively simple and not radically different from many I've done before. Yet didSelectRowAtIndexPath is only called on the first 5 cells of the table. After that, the debug statement does not appear when I tap. I've researched this issue here and have ruled out some possibilities that are mentioned in other questions:
- the table delegates are properly set.
- a GestureRecognizer (that I've set) is not swallowing the presses.
- willSelectRowAtIndexPath is not implemented
Below is my didSelectRowAtIndexPath. Let me know what else I can provide that can help solve this problem.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"didSelect");
NSArray *visibleCells = [partOfSpeechTable visibleCells];
UITableViewCell *cell = [visibleCells objectAtIndex:indexPath.row];
NSNumber *checkedState = [checkedStates objectAtIndex:indexPath.row];
if ([checkedState boolValue])
{
[cell setAccessoryType:UITableViewCellAccessoryNone];
[checkedStates setObject:[NSNumber numberWithBool:NO] atIndexedSubscript:indexPath.row];
}
else
{
[cell setAccessoryType:UITableViewCellAccessoryCheckmark];
[checkedStates setObject:[NSNumber numberWithBool:YES] atIndexedSubscript:indexPath.row];
}
[[tableView cellForRowAtIndexPath:indexPath] setSelectionStyle:UITableViewCellSelectionStyleNone];
}
(I can comment out all the accessory stuff and it makes no difference).
Thanks for any help.
It turned out that the containing view was shorter than the table itself. The table displayed in full, but the parts below the cut off of the containing view did not respond to user interaction. The solution was to increase the size of the containing view.
I have faced similar kind of issue its probably because of
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
return 20;
}
set the size of that cell so it can fit in view.
I had the same problem with Jason. In my case, only the cell on the first row wasn't calling didSelectRowAtIndexPath. I increased the containing view of the tableview in xib file (or you could do programmatically).
If didSelectRowAtIndexPath is called only for some cells and the app is running in the simulator then try to reset the simulator.
Menu Hardware > Erase All Content and Settings...
Related
I have a multi-selection UITableView created with prototype cells from a storyboard. I am able to make multiple selections,
I am finding that if I select the cell in row 0, I am unable to select any cell a multiple of 9 rows away. i.e. 9, 18, 27, etc.
If I deselect 0, I am then able to select row 9. Doing this, however, makes rows 0, 18, 27, etc unselectable.
I have implemented the following delegate method for logging, however, when clicking these unresponsive cells, this method does not get called.
-(NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if([[tableView cellForRowAtIndexPath:indexPath] isSelected]) {
NSLog(#"Already Selected");
} else {
NSLog(#"New Select");
}
return indexPath;
}
I have tested this on an iOS8.3 iPhone 6 Plus, an iOS9.2 iPhone 6S and an iOS9.2 iPhone 5S with the same results and the same distancing.
I don't believe it is a coincidence that there are 9 cells that fit on the screen at any one time. I assume it is connected with cell dequeuing and re-use but I've not been able to confirm that.
Does anyone have any direction on what it is that I am missing?
EDIT: Included tableView:cellForRowAtIndexPath
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
DKIFriendTableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier forIndexPath:indexPath];
NSArray *array;
switch ([indexPath section]) {
case 0:
array = [recommendedFilteredList objectAtIndex:[indexPath row]];
break;
case 1:
array = [fullFilteredList objectAtIndex:[indexPath row]];
break;
default:
return nil;
break;
}
BOOL selected = [selectedUsers containsObject:[array objectAtIndex:0]];
// Due to conflicts with searching table, set whether cell is selected or not.
if(selected)
[tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
else
[tableView deselectRowAtIndexPath:indexPath animated:NO];
[cell setNameText:[array objectAtIndex:1]];
[cell setProfilePictureImage:[UIImage imageWithData:[[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:[array objectAtIndex:2]]options:NSDataReadingMappedIfSafe error:nil]]];
return cell;
}
It is probably worth mentioning that the problem still occurs whether the selected boolean is checked and implemented or not. Just if it is not implemented, the search results cells do not come back as highlighted.
EDIT: Included numberOfSectionsInTableView: delegate method for further information relevant to the tableView:cellForRowAtIndexPath method.
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 2;
}
EDIT: Addition of sample project BitBucket repository link
After disposing of the code I previously had and attempting to recreate the views from scratch, I still experienced the same problems. I then created only this section of the project again in a new, separate project where I still experienced the same problem (this time over a different separation - n * 12 instead of n * 9).
Sample Project BitBucket link
i checked a project and find the problem.
- (void) setSelected:(BOOL)selected {
[_tickImage setHidden:!selected];
}
you override a method of tableviewcell however not called the super function. after doing this, all the flow in selection/deselection is acted like you wanted.
- (void) setSelected:(BOOL)selected {
[super setSelected:selected];
[_tickImage setHidden:!selected];
}
Your mistake is thinking that [[tableView cellForRowAtIndexPath:indexPath] isSelected] is a valid way to see if a row is selected.
If there's no visible cell for the row at indexPath, then [tableView cellForRowAtIndexPath:indexPath] returns nil. In Objective-C, you can send any message to nil, and get back nil/false/0. So if the row at indexPath has no visible cell, your test will always return false, saying the row isn't selected.
The correct way to check whether the table view thinks a row is selected is to ask the table view which rows are selected:
if ([tableView.indexPathsForSelectedRows containsObject:indexPath]) {
// row is selected
}
I have a tableview in a scrollview in a popover. When the view is presented, the bottom cell in tableview is not visible to the user. If I select all of the cells then deselect the fist cell, the out of view cell is deselected too. Has anyone come across this behaviour before? If so, how to approach it?
Now your job is to find all the visible cells in the tableview and then apply select/deselect to it.
UITableView *tableView = self.tableView;
// Or however you get your table view
NSArray *paths = [tableView indexPathsForVisibleRows];
// For getting the cells themselves
NSMutableSet *visibleCells = [[NSMutableSet alloc] init];
for (NSIndexPath *path in paths)
{
[visibleCells addObject:[tableView cellForRowAtIndexPath:path]];
}
// Now visibleCells contains all of the cells you care about.
-(void) tableView:(UITableView *)tableView didDeselectRowAtIndexPath:
(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:
(NSIndexPath *)indexPath {
//stuff
//as last line:
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
For that matter, deselectRowAtIndexPath can be called from anywhere at any time you want the row to be deselected.
[self.myTableView deselectRowAtIndexPath:[self.myTableView
indexPathForSelectedRow] animated: YES];
If you are using dequeueReusableCellWithIdentifier: change your cellForRowAtIndexPath: to use dequeueReusableCellWithIdentifier:forIndexPath:
In a UITableView cells get reused. That means it only produces as many as absolutely needed. As soon as a new one is coming onto the screen, the last one is "recycled" instead of initialising a whole new instance.
This makes your application run faster. It also means that you have to undo any changes you made, when recycling.
Selection status is one of them. The UITableView should manage this automatically for you, if it is dequeued with the relevant indexPath. If not, it wouldn't know whether that specific cell should be selected.
Let's start right off with some code :
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell"forIndexPath:indexPath];
Produit *object = self.objects[indexPath.row];
[cell.contentView addSubview:object];
return cell;
}
In a cell, I add a subView of type Produit, which is a subclass of UIView. This is how it looks like:
Editing all the stuff works fine except for when there are more cells than the size of the screen can allow. When that is the case, if I try and modify some info in one of the cells, it's as if the new info is added on top of the old one like this:
In this image, only the Button acts spooky but sometimes the text fields also appear on top of each other. What's more is that if I modify the cell on top, then if I scroll to the bottom of the table view, the last cell also gets modified. Last thing: when I add more cells after having produced this glitch, some of the new cells get the same 'Category' as the glitched one, it's like it's making a copy of it and puts in 'Category' the glitched title...
Can someone explain what's happening? How can I fix it? Here is some more code( not all of it, just the table view configuration)
-(void) addNewProduit:(UIBarButtonItem*) item {
if (!self.objects) {
self.objects = [[NSMutableArray alloc] init];
}
Produit* product = [[Produit alloc] init];
CGRect frame = CGRectMake(0, 0, self.frame.size.width, 44);
[product setFrame:frame];
[product initView];
[self.objects insertObject:product atIndex:0];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
[self.tableView insertRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
}
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
// Return NO if you do not want the specified item to be editable.
return YES;
}
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
[self.objects removeObjectAtIndex:indexPath.row];
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
} else if (editingStyle == UITableViewCellEditingStyleInsert) {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view.
}
}
dequeueReusableCellWithIdentifier: forIndexPath: gives you a cell, which might be a new cell, or it might be an old cell that's previously been shown, but has scrolled off the screen.
One quick fix is:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell"forIndexPath:indexPath];
Produit *object = self.objects[indexPath.row];
[cell.contentView.subviews makeObjectsPerformSelector:#selector(removeFromSuperview)];
[cell.contentView addSubview:object];
return cell;
This will resolve your issue in the least efficient way possible. It is likely to cause jittery animation when you scroll really fast, especially on older devices. I just intend it as an illustration of the problem you need to solve.
A more appropriate solution would reuse the view if it's already there, instead of creating a new one each time.
self.objects appears to contain views, which defeats the purpose of UITableView's really fast scrolling setup. You should just include data objects there, and then configure the views for an individual cell when it's time to show that one cell. IE, you don't want a view for each data object, you want 6 views that adapt to which data object currently needs to be displayed.
You are always adding more views when you re-use a cell by [cell.contentView addSubview:object];. One solution might be to tag the view when you add it and then remove any subview with the appropriate tag before adding another one.
I'm trying to understand why awakeFromNib is being called twice in my code. I currently have a tableview that has a special compressible cell that appears once at the end of the table. The first awakeFromNib is being called when the tableview is scrolled to the special cell at the end (which is fine I believe,as the tableview is reusing cells). However, whenever I tap the cell to expand the cell, the awakeFromNib is being called again.
Could anyone explain to me why awakeFromNib is being called twice? And how I could only make it only be called once?
Thanks
EDIT** Code people have requested
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section >= (NSInteger)[self.trip.destinations count]) {
GuestCell *cell = (GuestCell*)[tableView dequeueReusableCellWithIdentifier:GuestCellIdentifier forIndexPath:indexPath];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
[cell setupCellForGuests:self.trip.guests];
cell.guestExpanded = NO;
NSLog(#"RETURNING CELL");
return cell;
}
// For all other sections
return [self prepareCardCellForIndexPath:indexPath forHeightCalc:NO];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section >= (NSInteger)[self.trip.destinations count]) {
[self.tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
}
}
You're animating the reload of the expanding row. The table view implements this by creating another cell for the same index path, and animating a transition from the old cell to the new cell. It creates a second instance of your cell prototype, so the second instance also receives the awakeFromNib message. If you log self, you'll see that the address is different the second time.
I don't think you can avoid the creation of a second cell instance (and thus a second awakeFromNib) unless you get rid of the animation. Even then I'm not sure it will reuse the old cell.
If the cell with that nib is only one in the table, then my guess is that it has something to do with animations. I didn't check how tableview handles cells during animation, but for tableview header it asks for another instance and then performs animation (for example fade) - so the old instance is faded out and the new is faded in. At least that's what I think has the highest probability, if you are handling cells correctly.
I'm using a UISegmentedControl to switch a UITableView between two datasets (think favorites and recents). Tapping the segmented control reloads the tableview with the different data set.
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:anim];
When the user swipes to delete a row it works fine. HOWEVER when the user switches datasets via the segmented control, the DELETED CELL gets re-used without altering it's appearance (i.e. the red 'DELETE' button is still there and the row content is nowhere to be seen). This appears to be the opposite problem that most people are seeing which is the delete button not appearing.
This is the delete code:
- (UITableViewCellEditingStyle) tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath
{
return UITableViewCellEditingStyleDelete;
}
- (void) tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete)
{
if ([self.current isEqualTo:self.favorites])
{
Favorite *fav = self.favorites[indexPath.row];
NSMutableArray *mut = [self.favorites mutableCopy];
[mut removeObjectAtIndex:indexPath.row];
self.favorites = mut;
self.current = self.favorites;
[self.tableView deleteRowsAtIndexPaths:#[indexPath]
withRowAnimation:UITableViewRowAnimationAutomatic];
}
}
}
The tableview is set to single select, and self.tableView.editing == NO. I have also tried using [self.tableView reloadData] and deleting/inserting the difference in rows from one dataset to the next. Neither works.
The UITableViewCell I'm using supplies no backgroundView or selectedBackgroundView
[EDIT]
Segmented Control Value Changed:
- (IBAction)modeChanged:(id)sender
{
if (self.listMode.selectedSegmentIndex == 1)
{
self.current = self.favorites;
}
else
{
self.current = self.recents;
}
// Tryin this:
[self.tableView reloadData];
// Tried this:
// [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationFade];
}
// Only 1 Section per table
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
{
return [self.current count];
}
Oh for the love of...
I wasn't calling [super prepareForReuse]; in my UITableViewCell subclass.
UGH.
I ran into the same thing: to "delete" a custom UITableViewCell, I was removing it from the table and putting it onto another list, which the user could then display in a modal view when they have regrets and want to put it back. In iOS7 (but not iOS6), the cells so moved had the big ugly "DELETE" button still on them, despite calling setEditing:NO and so on. (And in addition, the rest of the cell content was not drawn at all, even though inspecting the cells in the debugger showed that all the subpanes were still there.)
Unlike Stephen above, I hadn't overridden prepareForReuse, so that wasn't the problem. But it was related: in my case, the cells weren't created with a reuse identifier:
self = [super initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
And per the docs, "If the cell object does not have an associated reuse identifier, this method is not called." But apparently, in iOS7 at least, it should be.
So the solution, in my case, was to explicitly call this [cell prepareForReuse] on each cell as I loaded it into the new table.