Identifying the table cell, when tapping on a Tap Recogniser - ios

I have a tableview in my View. The cells are created using custom Cells. I need to display a large string in the table view cells So I had added the text Label in a Scrollview. Also I need to execute some code when the user taps on table view cell. Please see the below code:
[cell.textLabelLine2 setFrame:CGRectMake(cell.textLabelLine2.frame.origin.x, cell.textLabelLine2.frame.origin.y, 500, cell.textLabelLine2.frame.size.height)];
cell.scrollView.contentSize = CGSizeMake(cell.textLabelLine2.text.length*10 , 10);
cell.scrollView.pagingEnabled = NO;
The problem is when the user touches above the Scroll View, the Tableview did select method will not be called. The solution I found for this problem is to add a gesture recogniser to the scroll view. But in this solution, we have no ways to check which cell(or which gesture recogniser) was selected. Could anyone help me to find a solution for this problem?

You can get to know the cell by the following code
if(gestureRecognizer.state == UIGestureRecognizerStateBegan) {
CGPoint p = [gestureRecognizer locationInView:[self tableView]];
NSIndexPath *indexPath = [[self tableView] indexPathForRowAtPoint:p];
if(indexPath != nil) {
UITableViewCell *cell = [[self tableView] cellForRowAtIndexPath:indexPath];
...
}
}

It's generally a bad idea putting scroll views inside scroll views. UITableView is also just a UIScrollView. That only kind of works if they are scrolling on different axis, i.e. the outer scroll view scrolling vertically and the inner scrolling horizontally.
For your specific scenario you would have to trigger the selection yourself. Once you have a reference to the cell you can ask the table view for the indexPath of it. Then you would call the delegate method for didSelectRow... yourself.

In the solution with the scrollview you are not able to scroll in the scrollview because the gestureRecognizer 'gets' the touch. Therefor I would not use the scrollview at all.
Make the label resize to its content like:
CGSize customTextLabelSize = [cell.customTextLabel.text sizeWithFont:cell.customTextLabel.font constrainedToSize:CGSizeMake(cell.customTextLabel.frame.size.width, 999999)];
cell.customTextLabel.frame = CGRectMake(cell.customTextLabel.frame.origin.x, cell.customTextLabel.frame.origin.y, cell.customTextLabel.frame.size.width, customTextLabelSize.height);
You also need to implement this in the heightForRowAtIndexPath
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
CGSize cellSize = [bigTextString sizeWithFont:customTextLabel.font constrainedToSize:CGSizeMake(generalCellWidth, 999999)];
return cellSize.height;
}
This way you can just use the didSelectRowAtIndex method.
If you really want to use the scrollview, add a button to your cell in the cellForRowAtIndexPath: method. Make the button just as big as the cell and add a button tag like this:
UIButton *cellButton = [UIButton buttonWithType:UIButtonTypeCustom];
cellButton.frame = CGRectMake(0, 0, cell.frame.size.width, cell.frame.size.height);
cellButton.tag = indexPath.row;
[cellButton addTarget:self action:#selector(cellButtonAction:) forControlEvents:UIControlEventTouchUpInside];
[cell.contentView addSubview:cellButton];
Then add:
-(void)cellButtonAction:(UIButton*)sender
{
//do something with sender.tag
}

Related

Correct way to setting a tag to all cells in TableView

I'm using a button inside a tableView in which I get the indexPath.row when is pressed. But it only works fine when the cells can be displayed in the screen without scroll.
Once the tableView can be scrolleable and I scrolls throught the tableview, the indexPath.row returned is a wrong value, I noticed that initially setting 20 objects, for example Check is just printed 9 times no 20.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
lBtnWithAction = [[UIButton alloc] initWithFrame:CGRectMake(liLight1Xcord + 23, 10, liLight1Width + 5, liLight1Height + 25)];
lBtnWithAction.tag = ROW_BUTTON_ACTION;
lBtnWithAction.titleLabel.font = luiFontCheckmark;
lBtnWithAction.tintColor = [UIColor blackColor];
lBtnWithAction.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin;
[cell.contentView addSubview:lBtnWithAction];
}
else
{
lBtnWithAction = (UIButton *)[cell.contentView viewWithTag:ROW_BUTTON_ACTION];
}
//Set the tag
lBtnWithAction.tag = indexPath.row;
//Add the click event to the button inside a row
[lBtnWithAction addTarget:self action:#selector(rowButtonClicked:) forControlEvents:UIControlEventTouchUpInside];
//This is printed just 9 times (the the number of cells that are initially displayed in the screen with no scroll), when scrolling the other ones are printed
NSLog(#"Check: %li", (long)indexPath.row);
return cell;
}
To do something with the clicked index:
-(void)rowButtonClicked:(UIButton*)sender
{
NSLog(#"Pressed: %li", (long)sender.tag);
}
Constants.h
#define ROW_BUTTON_ACTION 9
What is the correct way to get the indexPath.row inside rowButtonClicked or setting a tag when I have a lot of of cells in my tableView?
My solution to this kind of problem is not to use a tag in this way at all. It's a complete misuse of tags (in my opinion), and is likely to cause trouble down the road (as you've discovered), because cells are reused.
Typically, the problem being solved is this: A piece of interface in a cell is interacted with by the user (e.g. a button is tapped), and now we want to know what row that cell currently corresponds to so that we can respond with respect to the corresponding data model.
The way I solve this in my apps is, when the button is tapped or whatever and I receive a control event or delegate event from it, to walk up the view hierarchy from that piece of the interface (the button or whatever) until I come to the cell, and then call the table view's indexPath(for:), which takes a cell and returns the corresponding index path. The control event or delegate event always includes the interface object as a parameter, so it is easy to get from that to the cell and from there to the row.
Thus, for example:
UIView* v = // sender, the interface object
do {
v = v.superview;
} while (![v isKindOfClass: [UITableViewCell class]]);
UITableViewCell* cell = (UITableViewCell*)v;
NSIndexPath* ip = [self.tableView indexPathForCell:cell];
// and now we know the row (ip.row)
[NOTE A possible alternative would be to use a custom cell subclass in which you have a special property where you store the row in cellForRowAt. But this seems to me completely unnecessary, seeing as indexPath(for:) gives you exactly that same information! On the other hand, there is no indexPath(for:) for a header/footer, so in that case I do use a custom subclass that stores the section number, as in this example (see the implementation of viewForHeaderInSection).]
I agree with #matt that this is not a good use of tags, but disagree with him slightly about the solution. Instead of walking up the button's superviews until you find a cell, I prefer to get the button's origin, convert it to table view coordinates, and then ask the table view for the indexPath of the cell that contains those coordinates.
I wish Apple would add a function indexPathForView(_:) to UITableView. It's a common need, and easy to implement. To that end, here is a simple extension to UITableView that lets you ask a table view for the indexPath of any view that lies inside one of the tableView's cells.
Below is the key code for the extension, in both Objective-C and Swift. There is a working project on GitHub called TableViewExtension-Obj-C that illustrates the uses of the table view extension below.
EDIT
In Objective-C:
Header file UITableView_indexPathForView.h:
#import <UIKit/UIKit.h>
#interface UIView (indexPathForView)
- (NSIndexPath *) indexPathForView: (UIView *) view;
#end
UITableView_indexPathForView.m file:
#import "UITableView_indexPathForView.h"
#implementation UITableView (UITableView_indexPathForView)
- (NSIndexPath *) indexPathForView: (UIView *) view {
CGPoint origin = view.bounds.origin;
CGPoint viewOrigin = [self convertPoint: origin fromView: view];
return [self indexPathForRowAtPoint: viewOrigin];
}
And the IBAction on the button:
- (void) buttonTapped: (UIButton *) sender {
NSIndexPath *indexPath = [self.tableView indexPathForView: sender];
NSLog(#"Button tapped at indexpPath [%ld-%ld]",
(long)indexPath.section,
(long)indexPath.row);
}
In Swift:
import UIKit
public extension UITableView {
func indexPathForView(_ view: UIView) -> IndexPath? {
let origin = view.bounds.origin
let viewOrigin = self.convert(origin, from: view)
let indexPath = self.indexPathForRow(at: viewOrigin)
return indexPath
}
}
I added this as a file "UITableView+indexPathForView" to a test project to make sure I got everything correct. Then in the IBAction for a button that is inside a cell:
func buttonTapped(_ button: UIButton) {
let indexPath = self.tableView.indexPathForView(button)
print("Button tapped at indexPath \(indexPath)")
}
I made the extension work on any UIView, not just buttons, so that it's more general-purpose.
The nice thing about this extension is that you can drop it into any project and it adds the new indexPathForView(_:) function to all your table views without having do change your other code at all.
You are running into the issue of cell-reuse.
When you create a button for the view you set a tag to it, but then you override this tag to set the row number to it.
When the cell get's reused, because the row number is longer ROW_BUTTON_ACTION, you don't reset the tag to the correct row number and things go wrong.
Using a tag to get information out of a view is almost always a bad idea and is quite brittle, as you can see here.
As Matt has already said, walking the hierarchy is a better idea.
Also, your method doesn't need to be written in this way. If you create your own custom cell, then the code you use to create and add buttons and tags isn't needed, you can do it in a xib, a storyboard, or even in code in the class. Furthermore, if you use the dequeue method that takes the index path, you will always get either a recycled cell, or a newly created cell, so there is no need to check that the cell returned is not nil.

Accessing index of UICollectionViewCell from a ScrollView inside the cell

I have a collection view, and inside the collection view cell, I have a scrollView in which I want to display some pictures.
I placed a ScrollView inside my collectionviewcell in the storyboard and initialised the ScrollView inside my -collectionView:cellForItemAtIndexPath method as follows:
UIScrollView *scrollView = [cell viewWithTag:20];
scrollView.delegate = self;
[scrollView setContentSize:CGSizeMake((cell.frame.size.width*images.count), scrollView.frame.size.height)];
scrollView.userInteractionEnabled = NO;
[cell addGestureRecognizer:imagesScrollView.panGestureRecognizer];
Only the first image from the images array should be loaded when the view is first loaded and the rest of the images need to be loaded dynamically when the user scrolls the scrollview, in the -scrollViewDidScroll method. However, these images depend on the index of the collectionviewcell that my scrollView is lying in.
How can I access the index of the collectionviewcell from the -scrollViewDidScroll method?
Also, does this seem like a viable method to achieve what I'm trying to do, or will I need to subclass UICollectionViewCell?
Subclassing can be one of the solutions but you can get the indexPath of a UICollectionViewCell from its subviews too. The following code would hopefully serve your needs. Use it in your scrollViewDidScroll: method.
UIView *superview = scrollView;
while (![superview isKindOfClass:[UICollectionViewCell class]]) {
superview = superview.superview;
}
UICollectionViewCell *cell = (UICollectionViewCell *)superview;
NSIndexPath *indexPath = [self.collectionView indexPathForCell:cell];
NSLog(#"IndexPath: %#", indexPath);
Make sure you are not using a scrollView elsewhere in this viewController.

How to get the cell reference from a gesture of another item in the cell

I have a UITableView which has some custom cells. Within the custom cells I have a main UIImageView. When the cell is created I add a Tap Gesture Recogniser to the image view.
When the image is tapped I run the following:
- (void) handleImageTap:(UIGestureRecognizer *)gestureRecognizer {
NSLog(#"Image tapped");
UIImageView *imageView = (UIImageView *)gestureRecognizer.view;
// send the image instead of self when firing the segue
[self performSegueWithIdentifier:#"remindMeTurnInfo" sender:imageView];
}
I then pass the image into a new view controller in prepareForSegue method:
if ([segue.identifier isEqualToString:#"remindMeTurnInfo"]) {
UIImageView *imgView = (UIImageView *)sender;
MESPlayedTurnReminderViewController *VC = segue.destinationViewController;
VC.turnImage = imgView.image;
}
Question
1. I need a reference to the cell that the UIImageView is within, that was tapped. So the user taps one of the cells images, and I need to know which cell (indexPath) that image was tapped from. 2. I am finding that sometimes when the image is tapped the didSelectRowAtIndexPath is being called. This is incorrect and it should not be called, only the relevant handleTap method from the Gesture Rec. should be called. How can I ensure that the didSelectRowAtIndexPath is not called, as I need to run some other code when the cell is actually (correctly) selected.
There are two ways this is commonly done. Either add a tag to your image view in cellForRowAtIndexPath that's equal to indexPath.row, or search up through the hierarchy of views from the image view until you find one that's a UITableViewCell (or subclass). That can be done like this:
- (void) handleImageTap:(UIGestureRecognizer *)gestureRecognizer {
UIImageView *imageView = (UIImageView *)gestureRecognizer.view;
UIView *superview = imageView.superview;
while (![superview isKindOfClass:[UITableViewCell class]]) {
superview = superview.superview;
}
NSLog(#"indexPath.row is: %d", [self.tableView indexPathForCell:(UITableViewCell *)superview].row);
}
The reason to search rather than using something like imageView.superview.superview (which would work in iOS 6), is that the hierarchy can change in different versions of iOS, and in fact, it did change between iOS 6 and iOS 7.
As for your second question, it probably happens because you're accidentally tapping the cell rather than the image view. Other than making the image view larger so it's easier to tap, I don't see a fix for that.
1.- To get the cell you can check the superview of the UIImageView tapped
- (void) handleImageTap:(UIGestureRecognizer *)gestureRecognizer
{
UIImageView *imageView = (UIImageView *)gestureRecognizer.view;
UITableViewCell *cell = (UITableViewCell *)imageView.superview;
}
2.- To disable the cell selection on a UITableView
[self.yourTableView setAllowsSelection:NO];

How do I reach a sender's parent view?

I have a button inside a custom cell. When tapped the button presents a view via a modal segue.
So, here's my question, in the prepareForSegue:sender: I want to pull the indexPath of the cell which contains that button(sender) that I pushed. How do I get that cell's indexPath?
I thought I could do something like:
UITableViewCell * cell = sender.parent
Obviously it doesn't work that way.
Please help me out.
UPDATE Thanks to #rmaddy I've tried [[sender superview] superview] and got to the cell in which the button was held and to it's indexPath. Thanks for your answers guys!
Assuming sender is a UIVIew of some sort (such as a UIButton), then:
UIView *parentView = [(UIView *)sender superview];
If you are positive that the button's superview is the table cell then you can do:
UITableViewCell *cell = (UITableViewCell *)[(UIView *)sender superview];
Once you have the cell, use the table view's indexPathForCell: method.
Keep in mind that if you actually added the button to the cell's contentView or some other subview of the cell, then getting the cell from the button is a bit trickier.
You can access a view's superview with the superview property so
sender.superview
Just add in cellForRow... method
cellButton.tag = indexPath.row;
and get it back when this button is pressed or you have the reference of the button
try this
UIButton *btn = (UIButton*)sender;
UITableViewCell * cell = (UITableViewCell*)[btn superview];
NSIndexPath *indx = [myTable indexPathForCell:cell];
There is a hidden element added to the cell: UITableViewCellScrollView. UITableViewCellScrollView is added between the UITableViewCell and the content view. The first sender superview will return an UIView object corresponding the content view. The second superview will return an UITableViewCellScrollView and the third superview will return UITableViewCell. Then use the indexPathForCell method to get the cell's index.
UITableViewCell *cellView = (UITableViewCell *)[[[sender superview] superview] superview];
NSIndexPath *indexPath = [self.tableView indexPathForCell:cellView];

Getting index path of the table

I am using group of buttons with different images and tableview with custom cell in my project.every cell having group of components (like imageview,button,label,slider) when pressing each button one cell will be added.
My button image and also added to imageview of the cell.
How can i get the index of each cell when pressing the button again.
cell will be added when i press the button at first time.
when pressing second time i want to increment the label value inside the cell.so i need the indexpath to cell the cell from the tableview.
I'm not sure exactly what you're looking for, maybe you can clarify what you're trying to do.
The method indexPathForCell: is a UITableView method that gives you the index of a certain cell.
Clarify your question a bit and maybe we can help more.
You can call button touch event to get the cell of uitableview -
UIView *cellView = (UIVIew *)[button superview];
UITableViewCell *cell = (UITableViewCell *)[cellView superview];
NSIndexPath *indexPath = [self.tableview indexPathForCell:cell];
Without knowing the structure of your cells, table, or anything like that. this code will work.
-(NSIndexPath *) pathForButton:(UIbutton *) b {
UITableView *tv = nil;
UIView *superView = [b superview];
UIView *cell;
while(superview != nil && ![superview isKindOfClass:[UITableView class]]){
cell = superview;
superview = [superview superview];
}
if(superview != nil && cell != nil){
tv = (UITableView*)superview;
return [tv indexPathForCell:cell];
}
return nil;
}

Resources