What is the best way of preventing the user from selecting a cell inside a UITableView, but allowing my program to call selectRowAtIndexPath: on the table view?
I also want the controls in the UITableViewCell to remain interactive (i.e. allow touchesBegan: to be called on the UITableViewCell).
If I do [tableView setAllowsSelection:NO], calling selectRowAtIndexPath: does not do anything.
I realized that when you call selectRowAtIndexPath: programmatically, then the delegate method willSelectRowAtIndexPath: will not be called. However, if a user taps a cell, then this will be called. This method can return nil to prevent selection.
- (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
return nil;
}
This will ensure that only programmatic calls to selectRowAtIndexPath: will select a row and any taps to select a row will not.
Uncheck the box : User Interaction Enabled in TableView as well as TableViewCell
Related
I've implemented thedidDeselectRowAtIndexPath method in a UITableView which includes multiple selection.
For some reason, didDeselectRowAtIndexPath is not being called by the delegate. Any suggestions? (I haven't accidentally misspelled didSelectRowAtIndexPath).
Thanks!
Some things to note about cell selection:
didDeselectRowAtIndexPath is only called in a single-selection UITableView if the user clicks a different cell than the one already selected.
didSelectRowAtIndexPath is called to select the new row, and didDeselectRowAtIndexPath is called to deselect the previous row
In a UITableView with multiple-selection, the previous row is not being deselected, so your deselect is handled in didSelectRowAtIndexPath
You can grab the cell using the delegate method you've implemented and modify it after checking it's selection like so:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
if (cell.selected) {
// ... Uncheck
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
}
I currently use a tableView with multiple selection, and didDeselectRowAtIndexPath method is calling for me (unlike what is stated in third bullet point in answer above).
I did note that cells that were "preselected", did not call didDeselectRowAtIndexPath correctly unless I did three things:
In cellForRowAtIndexPath, when I find a cell I want to preselect (you might check for matching array entries, or a string like I did), set cell.selected = true for that cell
In cellForRowAtIndexPath, also make a call to method
selectRowAtIndexPath for the cell to preselect
In Interface Builder, uncheck the "Show Selection on Touch" checkmark for the tableView. Oddly enough, this was required to ensure that when a preselected item was tapped (ie should be deselected), that didDeselectRowAtIndexPath was called correctly.
Example code:
if productsReceived!.rangeOfString(cell.productName.text!) != nil {
cell.accessoryType = .Checkmark
tableView.selectRowAtIndexPath(indexPath, animated: true, scrollPosition: UITableViewScrollPosition.None)
cell.selected = true
}
I hope this helps someone who is trying to implement multiple selection, with preselected cells.
If I'm not mistaken, UITableView doesn't have a built in method for deselecting cells. I believe you need to call deselectRowAtIndexPath: in order for didDeselectRowAtIndexPath to be called.
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.
Ok so I have a custom animation being implemented inside willDisplayCell method. It is working fine when I scroll the view up and down. When I tap on one of the row, it will be pushed to another view controller to show more details and let user update the data.
The issue is when the user get back to the tableview. I called the [tableView reloadData] method inside the viewWillAppear to make sure updated data is shown. This will trigger the animation transition that I set up earlier.
My question is: Is there a way to only perform the animation when user scroll up/down the tableview, not when the reloadData is called?
If there's a way to mix between the willDisplayCell with scrollViewDidScroll or something along that line, it would be awesome.
Thanks!
The easiest solution would be to add a state flag that would tell the willDisplayCell whether it should actually animate.
Add a property to your UITableViewDelegate:
#property (nonatomic) BOOL shouldPreventDisplayCellAnimation;
Set the property before and after calling reloadData:
- (void)viewWillAppear:(BOOL)animated
{
…
self.shouldPreventDisplayCellAnimation = YES;
[self.tableView reloadData];
self.shouldPreventDisplayCellAnimation = NO:
}
Modify willDisplayCell to animate on condition
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (!self.shouldPreventDisplayCellAnimation) {
//animate
}
}
I have the following UITableViewCell (well, subclassed).
With didSelectRowAtIndexPath it is possible to capture that a cell has been selected in UITableViewController. My problem occurs due to the fact that directly pressing Choose User bypasses the selection of the cell.
How could I allow my UITableViewController to be aware that UITableViewCell foo has been pressed even if the user immediately hits Choose User?
N.B. I don't need the Selection capability per se, this was just by method of knowing that a user had tapped within a cell area.
You could just call the method directly. If we say that for each Choose User button we are setting the row number as the tag and assuming that you don't have sections so everything will happen in section 0 we could do.
- (void)hitChooseUser:(id)sender
{
// Do whatever you want for when a user hits the `Choose User` button
// Code......
// Then do this at the end or whenever you want to do it.
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:[sender tag] inSection:0];
// Also assuming you have created you have created you UITableView correctly.
[self tableView:myTableView didSelectRowAtIndexPath:indexPath];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// Do whatever it is you want.
}
I also found this link that may help you Manually call didSelectRowatIndexPath
You could also disable the user interaction with the cell itself by setting userInteractionEnabled: to NO for each cell in cellForRowAtIndexPath: so didSelectRowAtIndexPath: will only get called when you want to call it manually.
Do not call didSelectRowAtIndexPath: It is a UITableViewDelegate method and, where possible, should be used as such (meaning let the UITableView send messages to it). In addition, it creates an unnecessary dependency on UITableView implementation.
That being said, in order to achieve shared behavior that is performed either on button click, or on row selection, refactor it out into a common method that is not coupled with UITableViewDelegate
For example:
-(void)doSomethingCommon {
//do shared code here
}
-(void)chooseUserButtonPressed:(id)sender {
[self doSomethingCommon];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[self doSomethingCommon];
}
And if your UITableView shows more than one of these rows, for which you depend on knowing which corresponding model object is related to the cell, than you can use the tag property on UIView subclasses (usually something in your cell) to mark the row that the object is shown in.
I am attempting to select, in the view did appear method, a table cell programatically.
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:1 inSection:1];
[self.tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
my delegate,
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
does not get called.
The delegate gets called if i select the cells in the simulator with a mouse, however, just not programmatically.
Why can this be?
Because that's how the framework works. Try reading the docs. That's what they are for!
selectRowAtIndexPath:animated:scrollPosition:
Selects a row in the receiver identified by index path, optionally scrolling the row to a location in the receiver... Calling this method does not cause the delegate to receive a tableView:willSelectRowAtIndexPath: or tableView:didSelectRowAtIndexPath: message.
That's pretty easy to understand. The delegate method didSelectRowAtIndexPath: is not called if you select the row in code (programmatically). It is called only if the user selects the row.
And this makes perfect sense, because:
If you are selecting the row in code, you might not want the delegate method triggered.
If you do want the delegate method triggered, since you are in your own code, you can just call it.
You don't need the delegate method in order to learn that the row was selected, because you selected it in code - you cannot not know!