I created a fresh project (Xcode 4, Master-Detail application) just to see if I'm doing something wrong, but I still have the same problem. I want to call -reloadData when the user deselects a cell, so this is my code:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
NSLog(#"%s", __PRETTY_FUNCTION__);
}
-(void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"%s", __PRETTY_FUNCTION__);
[tableView reloadData];
}
-(NSIndexPath *)tableView:(UITableView *)tableView willDeselectRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"%s", __PRETTY_FUNCTION__);
return indexPath;
}
The problem is that didDeselectRowAtIndexPath and willDeselectRowAtIndexPath don't seem to be called. Is this the expected behavior? The docs for tableView:didDeselectRowAtIndexPath: state
Tells the delegate that the specified row is now deselected.
so I guess that it should work as I thought.
If you call deselectRowAtIndexPath:animated:, the delegate methods tableView:willDeselectRowAtIndexPath: and tableView:didDeselectRowAtIndexPath: message are not sent.
the documentation of tableView:willDeselectRowAtIndexPath: also says that
This method is only called if there is an existing selection when the
user tries to select a different row. The delegate is sent this method
for the previously selected row. You can use
UITableViewCellSelectionStyleNone to disable the appearance of the
cell highlight on touch-down.
It not worked for we when I used UITableViewCellSelectionStyleNone.
One other quirk that I've found — in IOS 5.1, at any rate — is that if you call reloadData on the table, you won't get didDeselectRowAtIndexPath for any selected rows. In my case, I adjust the cell layout slightly depending on whether it's selected or not, so I needed to manually do that work prior to reloading the table data.
I know this is an old question but I just ran into the same problem.
If you use
[tableView reloadData]
Then The table data is reloaded and no rows are selected behind the scenes - meaning
only
didSelectRowAtIndexPath
is ever called. I hope this helps someone who comes across this problem.
A clean way of solving this problem is to do the following:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("myCell", forIndexPath: indexPath)
// Do your cell setup work here...
//If your cell should be selected...
if cellShouldBeSelected {
tableView.selectRowAtIndexPath(indexPath, animated: false, scrollPosition: UITableViewScrollPosition.None)
}
return cell
}
This solves this entire problem of a cell not responding to being deselected after a tableView.reloadData()call happens.
When any cell is selected the first time, the -[UITableViewDelegate tableView:didDeselectRowAtIndexPath:] method is not called, but the -[UITableViewDelegate tableView:didSelectRowAtIndexPath:]. Just after selecting one more cell, the didDeselectRowAtIndexPath is called right after the didSelectRowAtIndexPath.
This is OK.
But if you have to show a cell as selected at the begining, (e.q. using UITableViewCellAccessoryCheckmark), then, after selecting another cell you probably want the didDeselectRowAtIndexPath method being called the first time, to deselect the previous cell.
The solution!
You have to call the -[UITableView selectRowAtIndexPath:animated:scrollPosition:] in the -[UITableViewDataSource tableView:cellForRowAtIndexPath:] to notify that a wanted cell is already selected.
Objective-C
#pragma mark - UITableViewDelegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
cell.accessoryType = UITableViewCellAccessoryCheckmark;
// saving the current selected row
SelectedRow = indexPath.row;
}
- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
cell.accessoryType = UITableViewCellAccessoryNone;
}
#pragma mark - UITableViewDataSource
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cell"];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"cell"];
}
// preventing selection style
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.textLabel.text = "Some text";
if (indexPath.row == SelectedRow) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
// just wanted to make it selected, but it also can scroll to the selected position
[tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
}
return cell;
}
Swift 3.1
// MARK: - UITableViewDelegate
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath)!
cell.accessoryType = UITableViewCellAccessoryType.checkmark
selectedRow = indexPath.row
}
override func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath)!
cell.accessoryType = UITableViewCellAccessoryType.none
}
// MARK: - UITableViewDataSource
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: "cell")
// preventing selection style
cell.selectionStyle = UITableViewCellSelectionStyle.none
cell.textLabel?.text = "some text"
if (indexPath.row == selectedRow) {
cell.accessoryType = UITableViewCellAccessoryType.checkmark
// just wanted to make it selected, but it also can scroll to the selected position
tableView.selectRow(at: indexPath, animated: false, scrollPosition: UITableViewScrollPosition.none)
}
return cell
}
You just have to set the selection to be "Multiple Selection" as Xcode allow you to deselect the cells in this mode only.
Xcode Screenshot
Set allowsMultipleSelection for that tableview to TRUE
self.tableView.allowsMultipleSelection = YES;
For me it's started working by adding ->super.setSelected(selected, animated: animated)
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)//This was missing
}
I think it's just simple mistake!
Why don't you use following:
[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
Instead of using just "tableView" in that line?
I guess, and pretty sure that above line would give you the solution! hope this helped you, if you looked back at your old question!!!
Kudos! :)
First of all, you have to set allowsMultipleSelection is true and set delegate by below code
self.tableView.allowsMultipleSelection = true
self.tableView.delegate = self
If you use tableView.reloadData() in didSelectRowAt delegate method, remove this.
in your custom cell, use selected method.
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
//write your code here for selection method
}
By this method, your cell state is selected. If you again click on selected cell, then didDeSelect delegate method will call automatically.
One source of this issue is setting isSelected directly on the table view cell, rather than telling the table view to select the cell via selectRow(at:animated:scrollPosition:) or deselect it via deselectRow(at:animated:).
The cell's isSelected property is not the source of truth that the table view uses to determine whether to call the delegate's didDeselect method (although a cell whose isSelected is set does seem to prevent the table view from calling the delegate's didSelect method—meaning that the cell can get in a state where it is impossible to select or deselect via the UI).
Related
Hi in my application i am using one tableview and each cell contains one collection view. When i select one cell in tableview that cell should highlight in light gray and previous should change to black this i am doing as like below.
-(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
collectionView.backgroundColor=[UIColor clearColor];
collectionViewTagValue=collectionView.tag;
nextFocusedIndex=indexPath;
selectedCollectionCellTag=collectionView.tag;
// Get previously selected collectionview tag value(Because we have many collectionvies in table)
NSIndexPath *previousPath=[NSIndexPath indexPathForRow:self->appdelegateObject.guideSelectionTag inSection:0];
// Get Tableview cell based on tag value - that tag value will be a tableview row number
DetailTableViewCell *cell = (DetailTableViewCell*)[self->guideDetailsTable cellForRowAtIndexPath:previousPath];
// Get indexpath of a selected cell in collection view
NSIndexPath *previouslySelectedCell=[NSIndexPath indexPathForRow:self->appdelegateObject.guideSelectionIndex inSection:0];
// Get collectionview cell based on indexpath value
MainCollectionViewCell *previousCell = (MainCollectionViewCell*)[cell.collection cellForItemAtIndexPath:previouslySelectedCell];
// Change celllabel background color to normal not highlight
previousCell.cellLable.backgroundColor=[UIColor colorWithRed:0.073 green:0.073 blue:0.073 alpha:1.0];
MainCollectionViewCell *currentCell = (MainCollectionViewCell*)[collectionView cellForItemAtIndexPath:indexPath];
currentCell.cellLable.backgroundColor=[UIColor lightGrayColor];
}
using above code sometimes both previous and current cells are getting highlighted with light gray.Even if i did this change in dispatch_main_queue also same behaviour i observed. Can any one please suggest best approach for this feature.
Note : For first cell selection i am getting collectionview.tag as -1.
Implemented following method to fix your issue:
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
let yourCell = tableView.cellForRow(at: indexPath)
yourCell?.backgroundColor = UIColor.black
}
This code in Swift, you need to convert it to Objective-C.
This method call for previous cell which you have selected, it being deselect.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath)
cell?.contentView.backgroundColor = UIColor.red
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath)
cell?.contentView.backgroundColor = UIColor.lightGray
}
//add condition in tableView delegate in didselect or diddeSelect, check index.section..
//for selected cell
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = (UITableViewCell *)[tableView cellForRowAtIndexPath:indexPath];
// check indexPath.Section here
if (!indexPath.section) == 0{
cell.contentView.backgroundColor = [UIColor yellowColor];
}
}
//for other cells
-(void) tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath{
// check indexPath.Section here
if (!indexPath.section) == 0{
cell.contentView.backgroundColor = [UIColor whiteColor];
}
}
// hope its works for you!
We have a UITableView, every row as a UICollectionView that supports an horizontal scroll, without paging enabled.
We has the cells registered for reuse,
// Setup for VenueFilterTableViewCell
NSString * cellIdentifier = #"VenueFilterTableViewCell";
VenueFilterTableViewCell *tbCell = [self.tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (tbCell == nil) {
tbCell = [[VenueFilterTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:cellIdentifier];
tbCell.selectionStyle = UITableViewCellSelectionStyleNone;
}
// Method for inflate the UICollectionView in the row
[tbCell setVenues:venues
loading:NO
atLocation:self.definedLocation
forFilter:filter
withDelegate:self];
cell = tbCell;
When I scroll the row horizontally the UICollectionView at the indexPath.row 0 in the UITableViewCell, the indexPath.row 3 (initially out of the screen) scroll at the same time. So, if after scroll horizontal, you move scroll down quickly you can see the row 3, and the row 7... and so on, scrolling at the same time.
I have a progress bar in each cell, for providing feedback to the user how far to the end of the horizontal scroll he is, but because of this reuse behaviour, each row involved (0 and 3 and 7) is messing up the progress of the other.
Any suggestions?
UPDATE
I added into the UITableView the next event for controlling when the cell is out of the screen and force the UICollectionView inside to stop scrolling. That enhanced a bit the performance, but eventually the issue happen again.
Code in the UITableViewController for detecting when the row is out of the screen:
- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
if ([tableView.indexPathsForVisibleRows indexOfObject:indexPath] == NSNotFound) {
// This indeed is an indexPath no longer visible
// Do something to this non-visible cell...
if ([cell isKindOfClass:[VenueFilterTableViewCell class]]) {
VenueFilterTableViewCell *tbCell = (VenueFilterTableViewCell *) cell;
[tbCell stopScrolling];
}
}
}
Code in the UITableViewCell with the UICollection View, in the reload content, apart from recovering the contentOffset, we need to re-enable the self.collectionView.scrollEnabled = YES;
- (void) stopScrolling {
self.collectionView.scrollEnabled = NO;
[self.collectionView setContentOffset:self.collectionView.contentOffset animated:NO];
}
My first answer was not helpful. so I have fixed in this one Sorry to put it in swift you can easily convert that in objc
I have implemented willDisplayCell and endDispayCell with following code
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
guard let cell = cell as? ScreenShotsCell else { return }
self.configureCell(for: indexPath, to: cell)
cell.collectionView.setContentOffset(cell.collectionView.contentOffset, animated: true)
cell.collectionViewOffset = storedOffsets[indexPath.row] ?? 0
}
//--------------------------------------------------------------------------------
func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
guard let cell = cell as? ScreenShotsCell else { return }
storedOffsets[indexPath.row] = cell.collectionViewOffset
cell.collectionView.setContentOffset(cell.collectionView.contentOffset, animated: true)
}
Main core logic is cell.collectionView.setContentOffset so it will stop scroll when it is not visible so it will fix the issue of other row's of tableview scrolling because of reuse cell
Hope it is helpful
Seems like a typical issue caused by cell reusing. If you really have a UICollectionView as a part of a cell, the best way is to create a Dictionary of stored offsets.
Quick example:
var storedOffsets: [Int: CGFloat] = [:]
in tableView:didEndDisplaying:forRowAt: store the offsets of a collectionView's contentOffset.
and
then in tableView:willDisplay:forRowAt: use these stored offsets to set the contentOffset of the collectionView in the cell.
When a UITableViewCell is selected, voice over announces "selected", I don't want voice over to say "selected". How can i achieve this ?
Things I have tried with no success:
Changed the cell accessibilityHint and accessibilityLabel
Changed the cell selectionStyle = UITableViewCellSelectionStyleNone
changed the cell accessibilityTraits = UIAccessibilityTraitButton
Question:
I don't want voice over to say "selected" when a cell is selected. How can i achieve this ?
I asked this as a code level support issue from Apple and got the following solution which works perfectly. Use a custom subclass of UITableViewCell where you override accessibilityTraits as in the following example:
class NoTraitCell: UITableViewCell {
override var accessibilityTraits: UIAccessibilityTraits {
get {
return UIAccessibilityTraitNone
}
set {}
}
}
You could try by deselecting the cell again:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
You can create custom cell class and override accessibilityTraits like this:
- (UIAccessibilityTraits)accessibilityTraits {
return UIAccessibilityTraitButton;
}
If you don't intend to use the selection feature of the tableview then don't use tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath). I know I've always used this as a "didTapOnRowAt" method, but a better way is to use willSelectRowAt:
func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
// code for when a row is tapped
return nil
}
The return nil means the cell won't actually be selected.
What worked for me was to set the cell's accessibilityLabel to " " (empty string doesn't work) on didSelectRow(), trigger a reload, then reset accessibilityLabel on next dequeue.
You can use accessibilityElementsHidden property for disabling voice accessibility.
If you don't want to hear a view in voice-over mode set the accessibilityElementsHidden property to true for that particular view(documentation)
In your case, for a UITableViewCell you can set it as true in tableView(_:cellForRowAt:) method
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// write code to create cell ....
cell.accessibilityElementsHidden = true
return cell
}
Note: You can also set the property in awakeFromNib() method for a custom class table view cell.
The only work around is prevent cell selection
- (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath;
{
return nil;
}
Add a Tap gesture on the cell and when the cell is tapped, do the what ever you want in cell selection inside the tap gesture.
How can I disable all cells, except the first 3 cells of a UITableView?
This returns nil if one of the disabled cells is selected:
- (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if ( indexPath.row >= 3 ) {
[[self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:2 inSection:0]] setAlpha:0.5];
return nil;
}
return indexPath; }
How can I make the disabled cells "visible"?
This works, but I only on start up, because the indexPath of every cell changes while scrolling (reuse cells):
for(int i = 3; i <= 100; i++) {
[[self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:0]] setAlpha:0.5];
}
You can disable the cells by setting the selectionStyle property, see the code below:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = ...
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
Another way to disable the cell selection is by disabling the user interaction of the cell. See the code below:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = ...
cell.userInteractionEnabled = NO;
}
Enjoy. :)
It's best to set the opacity of the disabled cells in tableView:cellForRowAtIndexPath:. As #Parcs correctly says in their answer, you can also set userInteractionEnabled for the disabled cells to NO there.
You can just ignore the taps on inactive cells in tableView:didSelectRowAtIndexPath:.
Since the other answer providing code is outdated only showing how it's done in obj-c, here is a swift solution.
To disable a cell you need to set the isUserInteractionEnabled flag of a UITableViewCell to false.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// dequeue cell
...
cell.isUserInteractionEnabled = false
return cell
}
For subclasses of UITableViewCell you probably also want to alter the appearance of the now disabled cell. You can do so by overriding the property to run additional code when the flag gets set.
override var isUserInteractionEnabled: Bool {
didSet {
super.isUserInteractionEnabled = isUserInteractionEnabled
// additional code to change text colors for example
}
}
This is a simple question that I thought would have an easy-to-find answer but didn't. I want to select a cell in a collectionview. The main problem is that I can't attach a gesture recognizer to a prototype cell. I want to grab the text from a label on the cell that is touched. I use the name in a different function in my view.
Or a simpler question: Is there a tutorial on tap selection from a list of items?
You have the method collectionView:didSelectItemAtIndexPath: in the delegate. This should fire when you collect the cell and give you the correct indexPath for that particular cell.
Use this indexPath in combination with the collectionView's cellForItemAtIndexPath: to access a particular cell.
Example:
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
[self manipulateCellAtIndexPath:indexPath];
}
-(void) manipulateCellAtIndexPath:(NSIndexPath*)indexPath {
UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:indexPath];
// Now do what you want...
}
And, as long as I'm here. Swift-version:
override func collectionView(collectionView: UICollectionView, didDeselectItemAtIndexPath indexPath: NSIndexPath) {
manipulateCellAtIndexPath(indexPath)
}
func manipulateCellAtIndexPath(indexPath: NSIndexPath) {
if let cell = collectionView?.cellForItemAtIndexPath(indexPath) {
// manipulate cell
}
}