How do I get the name of a selected collectionviewcell? - ios

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
}
}

Related

UITableView with UICollectionView in row weird scroll in different rows at the same time

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.

UICollectionViewCell how to stop some process in other cells using active one

I have collection view with 3 cells. Each cell contains one button. When I tap on the button in the first cell it starts animate itself. So my gaol is to dismiss animation in the current button if I tap for example on the button in the second/3rd cell and vice verse.
What is the better way to do it. I suppose to store maybe all buttons in some array and then check which one is active now and then switch off it and switch on other.
But maybe it's better to create some cell builder or smth like that.
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(VAXSettingsModeCell.reusableCellIdentifier(), forIndexPath: indexPath) as! VAXSettingsModeCell
cell.delegate = self
let title = modesStrings[indexPath.row]
cell.AnimatableButton.setTitle(title, forState: UIControlState.Normal)
}
You can store IndexPath of animated cell inside your ViewController
#interface MyViewController: ViewController
#property (strong, nonatomic) NSIndexPath *animatedCellIndexPath;
#end
When user tap the button inside cell you can save this cell's indexPath and stop animations for all visible cells and then start animation for tapped cell.
- (void)buttonTappedForCell:(UICollectionViewCell *)tappedCell {
self.animatedCellIndexPath = [self.collectionView indexPathForCell:tappedCell];
for (UITableViewCell *cell in [self.collectionView visibleCells]) {
[cell setAnimating:NO];
}
[tappedCell setAnimating:YES];
}
And for every new cell you can check if that cell must be animated (for example if user scrolls collection view)
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
cell = ...
[cell setAnimating:indexPath == self.animatedCellIndexPath];
return cell;
}
Create UICollectionViewCell reference on the animating cell and when you press on other cells (didSelectCell..) stop the animation on that cell. When you animating other cell change the reference to the animating cell. Have in mind that if you are using reuseIdentifier and the animating cell is not showing in the screen then the UICollectionView will reuse it and you might see the animating cell on the wrong position. In that case your solution with array will be the ideal but you have to track that the animating cell is not showing in the screen (or in the cellForRowAtIndexPath stop the animation on each cell and then check if the currentIndex path has to animate and start animating).
Update
You can do it without array. Just set an NSInteger activeCellIndex=indexPath.row and check that one.

Programmatically select itself inside UITableViewCell

I have a UITableViewCell with a UICollectionViewCell inside. I want the user to be able to scroll the UICollectionViewCell but when he taps anywhere inside the UITableViewCell I need it to be selected. Currently when the user taps the UITableViewCell outside the UICollectionViewCell it is selected properly, but when he taps inside the UICollectionViewCell nothing happens. My idea is to implement the collectionView: didSelectItemAtIndexPath: method inside the UITableViewCell and programmatically trigger a "self selection", but I can't seem to find a way to do this. If I store a reference to the table and the indexPath of the cell inside itself I will probably be able to do it, but I have a feeling that this would be a bad way of doing it. How to do this properly?
My guess is that the UITableViewCell's didSelectRowAtIndexPath: is blocking the UICollectionViewCell's collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:
I would first check if that's the case by putting breakpoints for the UITableViewCell's didSelectRowAtIndexPath: while selecting the UICollectionViewCell.
If that's the case, this answer might help you (comes with a nice tutorial too): https://stackoverflow.com/a/17120673/5465258
I think they have a similar problem to what you had.
Well, I found a solution for this, though I don't know if it's the best one.
First, I added a UITapGestureRecognizer to the UICollectionView inside my UITableViewCell, to override the component's one.
-(void)awakeFromNib
{
...
UITapGestureRecognizer *v_CollectionViewTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(onClickCollectionView)];
[self.m_CollectionView addGestureRecognizer:v_CollectionViewTap];
...
}
Second I created a block property to hold the code for the programatic selection of a row and created a method that calls this block, setting it as the action for the previously created UIGestureRecognizer.
typedef void (^OnClickTableViewCellBlock)();
#property (nonatomic, copy) OnClickTableViewCellBlock m_OnClickBlock;
Last, when I'm creating the UITableViewCell, in the tableView:cellForRowAtIndexPath: method, I pass a block that calls the tableview:didSelectRowAtIndexPath: one.
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
...
v_Cell.m_OnClickBlock = ^void()
{
[tableView.delegate tableView:tableView didSelectRowAtIndexPath:indexPath];
};
...
}
My solution was essentially the same as accepted answer, except I'm using Swift and decided to use delegation.
Inside your UITableViewCell subclass - CustomCell in this case - create your delegate as below (making sure to add a property for IndexPath), and wire up the UITapGestureRecognizer similar to the accepted answer:
protocol CustomCellDelegate {
func collectionViewTapped(forIndexPath indexPath: IndexPath)
}
class CustomCell: UITableViewCell {
...
var delegate: CustomCellDelegate?
var indexPath: IndexPath?
#objc func onTapCollection() {
if let indexPath = indexPath {
delegate?.collectionViewTapped(forIndexPath: indexPath)
}
}
override func awakeFromNib() {
super.awakeFromNib()
let collectionViewTap = UITapGestureRecognizer(target: self, action: #selector(onTapCollection))
collectionViewTap.numberOfTapsRequired = 1
self.collectionView?.addGestureRecognizer(collectionViewTap)
}
...
}
And then just have your UITableViewController implement your CustomCellDelegate, and assign the delegate and IndexPath for your cell in tableView(_:cellForRowAt:):
class MyViewController: UITableViewController {
...
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
...
// cell here is my CustomCell that I set up
cell.indexPath = indexPath
cell.delegate = self
return cell
}
...
}
extension MyViewController: CustomCellDelegate {
func collectionViewTapped(forIndexPath indexPath: IndexPath) {
self.tableView(self.tableView, didSelectRowAt: indexPath)
}
}

Prevent voice over (Accessibility) from announcing UITableViewCell as selected

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.

Why is -didDeselectRowAtIndexPath not being called?

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).

Resources