In my project I have custom UITableview cells, which happen to have a button in it. When this button is selected, it triggers a segue. In this segue I pass an object from the cell to the destination UIViewcontroller. I'm currently using the following code
else if ([[segue identifier] isEqualToString:#"Add"])
{
SearchAddRecordViewController *addRecordViewController = [segue destinationViewController];
NSIndexPath *path = [self.tableView indexPathForSelectedRow];
SearchCell *cell = [self.tableView cellForRowAtIndexPath:path];
NSLog(#"%#", cell.record);
addRecordViewController.workingRecord = cell.record;
}
It turns out I'm passing null, because the button isn't triggering the selection of the cell, therefore no indexPathForSelectedRow. My question is, how do I get the indexpath for the cell which button has been pressed.
Edit:
Answered
You declare an NSIndexpath variable in your custom cell's .h file and assign the indexPath to that variable in cellForRowAtindexPath. And Retrieve it and use it....Hope it will work
For anyone experiencing a similar problem, this is how I was able to solve this.
I first created the implementation and header files for a UIButton subclass with the NSIndexPath property. I assigned the uibutton in the Storyboard the superclass of this new custom unbutton with the NSIndexPath property. I added the following code to the segue identification step (the key was realizing i could use the sender object as the source of triggering the segue):
else if ([[segue identifier] isEqualToString:#"Add"])
{
SearchAddRecordViewController *addRecordViewController = [segue destinationViewController];
addRecordButton *button = (addRecordButton*) sender;
NSIndexPath *path = button.indexPath;
SearchCell *cell = [self.tableView cellForRowAtIndexPath:path];
addRecordViewController.workingRecord = cell.record;
}
You can do this in following manner, this helps you to uniquely identify which button is tapped and you can pass the appropriate values, I am just passing a string value for example
suppose OutputString is a string property in your destination view controller
if ([[segue identifier] isEqualToString:#"yourSegueIdentifier"]) {
UITableViewCell *clickedCell = (UITableViewCell *)[[sender superview] superview];
NSIndexPath *clickedButtonPath = [self.myTableView indexPathForCell:clickedCell];
[segue.destinationViewController setOutputString:#"XYZ"];
}
An alternative way to determine the indexPath that avoids subclassing UIButton would be:
CGPoint point = [button.superview convertPoint:button.center toView:self.tableView];
NSIndexPath *path = [self.tableView indexPathForRowAtPoint:point];
EDIT:
I've factored this into a nice method you can add to a Category on UITableView:
-(NSIndexPath*)indexPathOfCellComponent:(UIView*)component {
if([component isDescendantOfView:self] && component != self) {
CGPoint point = [component.superview convertPoint:component.center toView:self];
return [self indexPathForRowAtPoint:point];
}
else {
return nil;
}
}
Related
The feed of my app is managed by a UITableViewController where I set custom cells that are instances of a UITableViewCell sub class.
One property of that sub class is a UIButton called flameRelation:
#property (weak) IBOutlet UIButton* flameRelation;
I am creating a dynamic segue to a detail view controller depending on the class of the sender. If the sender is the cell itself, I pass a given set of data to my detail view controller. If the sender is the flameRelation button, I pass another.
To achieve this, I am looking at the following 2-step approach:
1) Since I need to know the indexpath.row of the cell where the FlameRelation button was pressed, I set the tag of that button in the UITableView method that sets each cell of my feed as follows:
- (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
//Set a bunch of things before setting UIButton flameRelation tag
[cell.flameRelation setTag:indexPath.row];
And I am printing the tag which allows me to check that the tag is being set when I scroll down my feed after launching my build (I see the numbers incrementing as I scroll down, as expected).
NSLog(#"The button tag for this cell is set to %ld",(long)[cell.flameRelation tag]);
2) In the prepareForSegue method that allows the user to transition to the detail view controller with the relevant information, I test the class of the sender first. If the class of the sender is UIButton, I attempt to pass the right information to the detail view controller by setting the value of the indexpath to the value of the tag:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if([segue.identifier isEqualToString:#"FlameDetail"]) {
FlameDetailViewController* flameDetailController = (FlameDetailViewController*) segue.destinationViewController;
if ([sender isKindOfClass:[FlameCellTableViewCell class]])
{
NSIndexPath* indexPath = [self.tableView indexPathForCell:sender];
NSLog(#"The indexPath is: %#", indexPath);
flameDetailController.flame = [self.feed flameAtIndex:indexPath.row];
NSLog(#"class for object sender: %#", NSStringFromClass([sender class]));
}
else if ([sender isKindOfClass:[UIButton class]])
{
NSLog(#"The button tag is set to %ld",(long)[sender tag]);
NSIndexPath* indexPath = [NSIndexPath indexPathForRow:[sender tag] inSection:0];
NSLog(#"The indexPath from UIButton is: %#", indexPath);
flameDetailController.flame = [self.feed flameAtIndex:indexPath.row];
NSLog(#"The value of indexPath.row is %ld",(long)indexPath.row);
NSLog(#"class for object sender: %#", NSStringFromClass([sender class]));
flameDetailController.findOutView.hidden = NO;
[flameDetailController.flameAction setTitle:#"CHAT NOW" forState:UIControlStateNormal];
NSLog(#"Yes, the sender is UIButton");
NSLog(#"The title of flameAction is set to %#", flameDetailController.flameAction.titleLabel.text);
}
}
Everything works well if I tap on the cell itself, but if I tap on the flameRelation button, I see in my logs that the getter on tag returns 0. Which obviously doesn't transfer the proper information to my target view controller.
Thanks!
First of all why you are converting your tag value to indexPath and then getting your model data with the row number?
Your code:
NSIndexPath* indexPath = [NSIndexPath indexPathForRow:[sender tag] inSection:0];
flameDetailController.flame = [self.feed flameAtIndex:indexPath.row];
Try this instead:
flameDetailController.flame = [self.feed flameAtIndex:sender.tag];
I do not see a reason why this approach would not work. If it does not you need to share more code/details.
However, I would want to suggest another alternate (no so great though):
UITableViewCell *clickedCell = (UITableViewCell *)[[sender superview] superview];
NSIndexPath *clickedButtonPath = [self.tableView indexPathForCell:clickedCell];
I am trying to execute an IBAction when a long-press is performed on a cell in a UITableView. The action involves the content of the cell so I need to get the indexPath in order to retrieve the content from a dictionary in local storage. The IBAction method is defined in the MasterViewController.m file which contains the UITableView methods and is subclassed from UITableViewController. I have tried all of the following, and they all return null instead of the indexPath for the cell.
UITableViewCell *cell = (UITableViewCell *)self;
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
NSIndexPath *indexPath = [self.tableView indexPathForCell:sender];
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
I have also seen a few years-old answers to similar questions that use the position of the cell in the view, but I can't get any of those to work either.
UPDATE:
The IBAction, sendToPB, is being defined in a subclass of UITableViewController. There is a long-press gesture recognizer added to the cell in Interface Builder, with Sent Actions connected to sendToPB. The action is supposed to be copying the content of the cell to the clipboard when you long-press on the cell in the table view. All the ways I have tried so far return null for indexPath.
- (IBAction)sendToPB:(id)sender {
NSIndexPath *indexPath = [self.tableView indexPathForCell:sender];
NSString *object = self.objects[indexPath.row];
UIPasteboard *pb = [UIPasteboard generalPasteboard];
NSString *pressedCellText = [[Data getAllNotes] objectForKey:object];
[pb setString: pressedCellText];
}
UPDATE:
I have found two problems with this approach. First, the long-press gesture doesn't actually select the row, which is why all of the options that used indexPathForSelectedRow don't work. Second, sender is the gesture recognizer, and not the cell or row, so using sender also produces a null value for indexPath. With these two factors in mind, how else can you detect which cell you performed the long-press on?
You can get indexPath Like This on longPressGesture!
-(void)handleLongPress:(UILongPressGestureRecognizer *)gestureRecognizer
{
CGPoint p = [gestureRecognizer locationInView:self.myTableView];
NSIndexPath *indexPath = [self.myTableView indexPathForRowAtPoint:p];
if (indexPath == nil) {
NSLog(#"long press on table view but not on a row");
}
else if (gestureRecognizer.state == UIGestureRecognizerStateBegan)
{
NSLog(#"long press on table view at row %d", indexPath.row);
}
else
{
NSLog(#"gestureRecognizer.state = %d", gestureRecognizer.state);
}
}
may Be this Link will help you a little more
Declare variable 1st in .h file as
NSIndexPath *hitIndex;
then on long press method u can get the position of cell & hence indexpath
(void)longPressMethod:(UIButton *)btn
{
CGPoint hitPoint = [btn convertPoint:CGPointZero toView:tbl_traits];
hitIndex = [tbl_traits indexPathForRowAtPoint:hitPoint];
}
You can do it using Gesture Recognizers. Hope these snippets help.......
In your .h file
#interface yourClass ()
{
UILongPressGestureRecognizer *longPressRecognizer;
}
In viewDidLoad,
longPressRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(longPressDetected:)];
longPressRecognizer.minimumPressDuration = 2;
longPressRecognizer.numberOfTouchesRequired = 1;
longPressRecognizer.delegate = self;
In cellForRowAtIndexPath, just before return statement
[cell addGestureRecognizer:longPressRecognizer];
And at the end,
- (void) longPressDetected:(UILongPressGestureRecognizer *)recognizer
{
UITableViewCell *selectedCell = (UITableViewCell *)recognizer.view;
// Your required code here
}
Edit: Thanks to #RegularExpression
First be sure to set the delegate of your tableView
self.myTableView.delegate = self
I believe this is what you are looking for:
Swift
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
//save the indexPath.row as an integer inside a property or pass it to your action
}
You can then save the index of from the above method or simple call your action passing that index inside the above method.
Objective-C
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
//save the indexPath.row as an integer inside a property or pass it to your action
}
If this is inside a UITableViewController subclass, then casting self (which is an instance of the UITableViewController subclass) to UITableViewCell will not return the cell selected.
There is really 2 ways of doing this:
1- The easy way: Don't use an IBAction and just implement the delegate methods:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
2- The relatively easy way: in your IBaction, you can get the selected cell by using the property:
self.tableview.indexPathForSelectedRow
This property will give you the indexpath of the selected cell.
If you attached the UILongPressGestureRecognizer to the cell by calling cell.addGestureRegognizer(_: _: _:) you should retrieve the cell from the sender by doing
let touchedView = sender.view as! UITableViewCell
Anyway, the best way to achieve this usually is by inspecting the dataSource instead of calling the UITableViewDataSource methods
You could subclass UITableViewCell and add an IVar for the index path and set that when cellForRowAtIndexPath is called for the UITableView dataSource protocol.
I am having trouble figuring out how to get the indexPath for the selected row by pressing a button inside a custom UITableViewCell.
I have in my cellForRowAtIndexPath:
self.rCell.reportUserButton.tag = indexPath.row;
and in my prepareForSegue:
ReportUserViewController *reportUserVC = segue.destinationViewController;
reportUserVC.message = [self.receivedMessages objectAtIndex:self.rCell.reportUserButton.tag];
here is my issue...
self.rCell.reportUserButton.tag is set to the value of the last cell in view.
If I want the first cell, the last cell is always being set.
How do I get the indexPath for the cell that has the active Report User button????
cellForRowAtIndexPath call when cell created and reused in table view, you assign value like below code means only last reused cell indexPath assigned to your cell.
self.rCell.reportUserButton.tag = indexPath.row;
Use delegate in cell to pass values
// In CustomCell Class
#protocol yourdelegate <NSObject>
-(void)passCellIndexAction:(CustomCell *)cell;
#end
-(void)buttonClickAction
{
[delegate passCellIndexAction: self];
}
// In View controller class
-(void)passCellIndexAction:(CustomCell *)cell
{
ReportUserViewController *reportUserVC = segue.destinationViewController;
reportUserVC.message = [self.receivedMessages objectAtIndexcell.reportUserButton.tag];
}
There is another way of doing it:
- (NSIndexPath *)indexPathForRowAtPoint:(CGPoint)point
For example if using a custom Details button and a segue from that button to a details view:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"news-detail"]){
NSIndexPath *indexPath = [self.myTableView indexPathForRowAtPoint: ((UIButton *)sender).superview.superview.center];
NSArray *keys = [self.dataSource allKeys];
NSDictionary *newsItem = [[self.dataSource objectForKey:[keys objectAtIndex:indexPath.section]] objectAtIndex:indexPath.row]; ;
[(MyDetail_ViewController *) segue.destinationViewController setDataItem:newsItem];
}
}
You should use the delegate method on click of your cell button to tell it about the tableViewController.
#protocol cellDelegate <NSObject>
-(void)calledOnButtonClick:(CustomCell *)cell;
#end
Then on button Click event of your cell call the delegate like
if ([delegate respondsToSelector:#selector(calledOnButtonClick:)])
{
[delegate calledOnButtonClick:self];
}
Now at your tableViewController
-(void)calledOnButtonClick:(CustomCell *)cell
{
NSIndexPath *iPath = [_tableView indexPathForCell:cell];
}
it will give you the correct index path
This will probably take two seconds to answer, but my search skills have not gotten me very far on this issue. I am performing a segue but I'm not sure how to grab the id on the destination. Here is my code on the tableview controller.
- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath: NSIndexPath *)indexPath{
NSLog(#"reaching accessoryButtonTappedForRowWithIndexPath:");
[self performSegueWithIdentifier:#"leads_calls_to_detail" sender:[[self.leads_calls objectAtIndex:indexPath.row] objectForKey:#"ID"]];
}
What do I have to create on my destination view controller to be able to grab the id that I'm attempting to pass, or is the way I'm performing my segue incompatible with what I am attempting?
You should just pass values to the destinationViewController inside prepareForSegue: and pass self as the sender.. try using something like:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([[segue identifier] isEqualToString:#"leads_calls_to_detail"])
{
YourViewController *controller=(YourViewController *)segue.destinationViewController;
NSIndexPath *path = [self.tableView indexPathForSelectedRow];
//Or rather just save the indexPath in a property in your currentViewController when you get the accessoryButtonTappedForRowAtIndexPath callback, and use it here
controller.yourPropertyToSet = [self.leads_calls objectAtIndex:path.row];
}
}
And also according to Rob Mayoff, you can grab the index path for the row that the accessory was tapped at by using something like this:
NSIndexPath *indexPath = [self.tableView indexPathForCell:sender];//where sender is the sender passed in from prepareForSegue:
How to find indexPath for tapped accessory button in tableView
So, using storyboard you can create a segue from the UITableViewCell from the first tableViewController to a detailViewController.
Not too complicated, however, when a UISearchBarDisplayController is introduced into the storyboard mix, how can you segue the results cell to the detailViewController?
I am able to search without a problem, I followed this tutorial: http://clingingtoideas.blogspot.com/2010/02/uitableview-how-to-part-2-search.html
All I can do is select a row from the search, it turns blue and doesn't go to the detailViewController.
I have implemented the method prepareForSegue, which works for the non searched cells, but can't figure out this one.
Here's the solution that's based on the comment by #James Chen. Also using a different IndexPath depending on which state the table is in.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"toDetail"]) {
Person *person = nil;
if (self.searchDisplayController.active == YES) {
NSIndexPath *indexPath = indexPath = [self.searchDisplayController.searchResultsTableView indexPathForSelectedRow];
NSLog(#"segue - section: %d, row: %d", indexPath.section, indexPath.row);
person = [self.filteredPersonArray objectAtIndex:indexPath.row];
}
else {
NSIndexPath *indexPath = indexPath = [self.tableView indexPathForSelectedRow];
NSLog(#"segue - section: %d, row: %d", indexPath.section, indexPath.row);
person = [self.personArray objectAtIndex:indexPath.row];
}
[[segue destinationViewController] setPerson:person];
}
}
I tried your solution and found that prepareForSegue is called twice
due to the life cycle and didSelect... -> performSegueWithIdentifier.
self:prepareForSegue: object on destination controller is set
(with wrong index) because
dest:viewDidLoad: the destination controller view is loaded after which
self:didSelectRow...: the index is known and properly set.
self:prepareForSegue: object is now correct but has no side effect.
I then focused on didSelect... and came up with this solution where I deleted the segue and pushed the view programmatically:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
DestTableViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:#"DestViewController"];
CustomObj *custObj = nil;
if (tableView == self.searchDisplayController.searchResultsTableView) {
custObj = [filteredListContent objectAtIndex:indexPath.row];
} else {
storeToDetail = [self.fetchedResultsController objectAtIndexPath:indexPath];
}
controller.custObj = custObj;
[self.navigationController setNavigationBarHidden:NO];
[self.navigationController pushViewController:controller animated:YES];
// presentViewController::animated:completion is always full screen (see problem below)
}
I then experienced some problems going back because I follow a segue
from a mapView, which lead to:
//DestinationViewController
- (IBAction)back:(id)sender
{
[self.navigationController popToRootViewControllerAnimated:YES]; // list
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil]; // map
}
which is not the way to do it but for now it works.
However, the first part is easy and clean, and maybe it works for you too?!
Ok I think I got it, it seems like a bit of a hack but it works for my purposes:
I am using storyboard:
I have a UITableview controller with UISearchBarDisplayController directly on top of it. No code just drag and drop.
From there, I followed this tutorial to get the search bar to search correctly http://clingingtoideas.blogspot.com/2010/02/uitableview-how-to-part-2-search.html
However prepareForSegue: would only let me display a cell from the original array, not with the search array.
So I used didSelectRowAtIndexPath:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (savedSearchTerm) {
searchRowSelected = indexPath.row; //<-- the trick that worked
[self performSegueWithIdentifier:#"ShowDetail" sender:self];
}
}
searchRowSelected is an int variable that I declared at the top of the class.
didSelectRowAtIndexPath: knew which row I was selecting, but prepareForSegue didn't. Thats why I needed that variable.
This is how I used it in prepareForSegue:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if ([segue.identifier isEqualToString:#"ShowDetail"]) {
dvc = [segue destinationViewController];
NSIndexPath* path = [self.tableView indexPathForSelectedRow];
int row = [path row];
if (savedSearchTerm){ //set the detailViewController with the searched data cell
myDataClass* c = [searchResults objectAtIndex:searchRowSelected];
dvc.myDataClass = c;
}else{ //set the detailViewController with the original data cell
myDataClass* c = [array objectAtIndex:row];
dvc.myDataClass = c;
}
}
}
Also use this code to clean up savedSearchTerm
-(void) searchDisplayControllerWillEndSearch:(UISearchDisplayController *)controller{
[self setSavedSearchTerm:nil];
}
If anyone has a better solution I'm all ears :)