NSIndexPath of UITableViewCell subview? - objective-c

Is there any was to get the NSIndexPath of a UITableViewCell's contentView subview?
In my case, I have a table view in which each row is split into 3 buttons. I can use the button tag to keep track of which column it is but I also need to know what row of the table view it is a subview of (when the user taps the button).
Because the button blocks the entire actual table view row, didSelectRowAtIndexPath is never called (which is expected), so I can't get it that way.
I have tried just [thisButton superview] but that doesn't seem to work. Any thoughts?

I assume the button is sending its UIControlEventTouchUpInside event message to your view controller? Then you could do something like:
- (void)buttonPressed:(UIButton *)button
{
CGPoint location = [button.superview convertPoint:button.center toView:self.tableView];
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:location];
[self doSomethingWithRowAtIndexPath:indexPath];
}

One possible option is when making the cell for index path you can assign the tag to be 3 * row_number + <the tag number>. then just divide the tag by 3 to get the row and %3 to get the button.

You don't need to call button.superview. You can go straight from any subview in the tree right to the table view
Objective-C
- (NSIndexPath *)indexPathForCellContainingView:(UIView *)view inTableView:(UITableView *)tableView {
CGPoint viewCenterRelativeToTableview = [tableView convertPoint:CGPointMake(CGRectGetMidX(view.bounds), CGRectGetMidY(view.bounds)) fromView:view];
NSIndexPath *cellIndexPath = [tableView indexPathForRowAtPoint:viewCenterRelativeToTableview];
return cellIndexPath
}
Swift
func indexPathForCellContainingView(view: UIView, inTableView tableView:UITableView) -> NSIndexPath? {
let viewCenterRelativeToTableview = tableView.convertPoint(CGPointMake(CGRectGetMidX(view.bounds), CGRectGetMidY(view.bounds)), fromView:view)
return tableView.indexPathForRowAtPoint(viewCenterRelativeToTableview)
}

Related

tvOS UITableView scroll to cell

I'm starting a new project for tvOS. I have created a programmatically UITableView inside my viewcontroller. Inside the UITableView i have created 5 cells. Until now everything works, but the problem happens when i'm trying to programmatically scroll to cell 4 so i'm doing:
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:3
inSection:0];
[tableView scrollToRowAtIndexPath:indexPath
atScrollPosition:UITableViewScrollPositionTop
animated:YES];
But the code above does nothing. It always start at first cell and highlight it as the selected one. I want to be able to programmatically start at another cell and it most be highlight so when i use the arrow keys down or up it scroll from there and not first cell. Please help me in doing that
You should do next:
In viewController that respond for UITableView, implement next function:
- (UIView *)preferredFocusedView {
return self.tableView;
}
where tableView is your table, and after that, override next function
- (NSIndexPath *)indexPathForPreferredFocusedViewInTableView:(UITableView *)tableView {
return [NSIndexPath indexPathForRow:_exerciseIndex inSection:0];
}
where _exerciseIndex index that you need to focus.

Cell selection issue with EasyTableView

I have what must be a simple problem with EasyTableView (https://github.com/alekseyn/EasyTableView)
I have a number of horizontally scrolling tables that function properly.
I am able to select a cell and perform a segue, however, once the new view controller is dismissed, I am no longer able to select that cell and perform the same action until I have selected another cell in the same table.
My question is: How can I deselect previously selected the cell programmatically to renable this particular action.
Thanks in advance!
The selectedIndexPath is intentionally persistent in case a user scrolls the selected tableview cell offscreen and then back again. If you don't want this persistence please add the line shown below, after the delegate method (in EasyTableView.m):
- (void)setSelectedIndexPath:(NSIndexPath *)indexPath {
if (![_selectedIndexPath isEqual:indexPath]) {
NSIndexPath *oldIndexPath = [_selectedIndexPath copy];
_selectedIndexPath = indexPath;
UITableViewCell *deselectedCell = (UITableViewCell *)[self.tableView cellForRowAtIndexPath:oldIndexPath];
UITableViewCell *selectedCell = (UITableViewCell *)[self.tableView cellForRowAtIndexPath:_selectedIndexPath];
if ([delegate respondsToSelector:#selector(easyTableView:selectedView:atIndexPath:deselectedView:)]) {
UIView *selectedView = [selectedCell viewWithTag:CELL_CONTENT_TAG];
UIView *deselectedView = [deselectedCell viewWithTag:CELL_CONTENT_TAG];
[delegate easyTableView:self
selectedView:selectedView
atIndexPath:_selectedIndexPath
deselectedView:deselectedView];
// Add this line here!
_selectedIndexPath = nil;
}
}
}

update a UILabel when the cell in UITableView is selected

A really simple question here. I have a label on one view and a UITableView on the previous view. I have got a segue triggered when the user selects the row and I want the label to be updated with the text from that row. Here's one example, the code is pretty obvious.
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *countrySelection;
switch (indexPath.section) {
case kFirstSection:
countrySelection = [[NSString alloc]
initWithFormat:#"The country you have chosen is %#",
[self.MyCountries objectAtIndex: indexPath.row]];
[self performSegueWithIdentifier:#"doneResults" sender:self];
self.countryResult.text = countrySelection;
break;
The label isn't updated and I just don't know what should be done.
Thanks in advance!
These kind of things really need to be set on the View Controller that owns them. Use a public property to pass the value of the selected country to that view controller as outlined below:
First, create a property called something like:
#property(non atomic,strong) NSString *countryChosen;
in the destination View Controller, and make sure to #synthesize it
No reason to create another property for the IndexPath. Just use
// Pass along the indexPath to the segue prepareForSegue method, since sender can be any object
[self performSegueWithIdentifier:#"doneResults" sender:indexPath];
in the didSelectRowAtIndexPath method.
Then in the prepareForSegueMethod:
MyDestinationViewController *mdvc = segue.destinationViewController;
NSIndexPath *indexPath = (NSIndexPath *)sender;
mdvc.countryChosen = [self.MyCountries objectAtIndex: indexPath.row]];
On the viewDidLoad event of the Destination VC, just use:
self.countryResult.text = countryChosen;
* EDIT *
To deal with a datasource that has multiple sections, just use the same logic that you have in the cellForRowAtIndexPath.
NSDictionary *selRow = [[self.countriesIndexArray valueForKey:[[[self.countriesIndexArray allKeys] sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)] objectAtIndex:indexPath.section]] objectAtIndex:sindexPath.row];
Change this to suit your needs, but basically you are implementing the same logic that you would to display a cell, except you are specifying the indexPath (both section and row) that you want.
Then something like the following to set that property on the destination VC:
self.countryResult.text = [selRow valueForKey#"Country"];
In your current view controller create a new property for the indexPath of the cell the user selected, like this:
#property(strong,nonatomic) NSIndexPath *path;
#synthesize it and then when a user selects a row, set it by using
self.path = indexPath;
When you perform a segue, it will always call
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
So what you can do now when prepareForSegue: gets called, is the following:
/* if this is not the only segue you are performing you want to check on the identifier first to make sure this is the correct segue */
NSString *countrySelection = [[NSString alloc]
initWithFormat:#"The country you have chosen is %#",
[self.MyCountries objectAtIndex: self.path.row]];
segue.destinationViewController.countryResult.text = countrySelection;
/* after creating the text, set the indexPath to nil again because you don't have to keep it around anymore */
self.path = nil;
For this to work the view controller you want to show after selecting the cell must have a property for the UILabel, on which you are trying to set the text.

Getting row of UITableView cell on button press

I have a tableview controller that displays a row of cells. Each cell has 3 buttons. I have numbered the tags for each cell to be 1,2,3. The problem is I don't know how to find on which cell a button is being pressed. I'm currently only getting the sender's tag when one of the buttons has been pressed. Is there a way to get the cell row number as well when a button is pressed?
You should really be using this method instead:
CGPoint buttonPosition = [sender convertPoint:CGPointZero toView:self.tableView];
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:buttonPosition];
Swift version:
let buttonPosition = sender.convert(CGPoint(), to:tableView)
let indexPath = tableView.indexPathForRow(at:buttonPosition)
That will give you the indexPath based on the position of the button that was pressed. Then you'd just call cellForRowAtIndexPath if you need the cell or indexPath.row if you need the row number.
If you're paranoid, you can check for if (indexPath) ... before using it just in case the indexPath isn't found for that point on the table view.
All of the other answers are likely to break if Apple decides to change the view structure.
Edit: This answer is outdated. Please use this method instead
Try this:
-(void)button1Tapped:(id)sender
{
UIButton *senderButton = (UIButton *)sender;
UITableViewCell *buttonCell = (UITableViewCell *)[senderButton superview];
UITableView* table = (UITableView *)[buttonCell superview];
NSIndexPath* pathOfTheCell = [table indexPathForCell:buttonCell];
NSInteger rowOfTheCell = [pathOfTheCell row];
NSLog(#"rowofthecell %d", rowOfTheCell);
}
Edit: If you are using contentView, use this for buttonCell instead:
UITableViewCell *buttonCell = (UITableViewCell *)senderButton.superview.superview;
I would recommend this way to fetch indexPath of cell which has any custom subview - (compatible with iOS 7 as well as all previous versions)
-(void)button1Tapped:(id)sender {
//- (void)cellSubviewTapped:(UIGestureRecognizer *)gestureRecognizer {
// UIView *parentCell = gestureRecognizer.view.superview;
UIView *parentCell = sender.superview;
while (![parentCell isKindOfClass:[UITableViewCell class]]) { // iOS 7 onwards the table cell hierachy has changed.
parentCell = parentCell.superview;
}
UIView *parentView = parentCell.superview;
while (![parentView isKindOfClass:[UITableView class]]) { // iOS 7 onwards the table cell hierachy has changed.
parentView = parentView.superview;
}
UITableView *tableView = (UITableView *)parentView;
NSIndexPath *indexPath = [tableView indexPathForCell:(UITableViewCell *)parentCell];
NSLog(#"indexPath = %#", indexPath);
}
This doesn't require self.tablview either.
Also, notice the commented code which is useful if you want the same through a #selector of UIGestureRecognizer added to your custom subview.
There are two ways:
#H2CO3 is right. You can do what #user523234 suggested, but with a small change, to respect the UITableViewCellContentView that should come in between the UIButton and the UITableViewCell. So to modify his code:
- (IBAction)button1Tapped:(id)sender
{
UIButton *senderButton = (UIButton *)sender;
UITableViewCellContentView *cellContentView = (UITableViewCellContentView *)senderButton.superview;
UITableViewCell *tableViewCell = (UITableViewCell *)cellContentView.superview;
UITableView* tableView = (UITableView *)tableViewCell.superview;
NSIndexPath* pathOfTheCell = [tableView indexPathForCell:tableViewCell];
NSInteger rowOfTheCell = pathOfTheCell.row;
NSLog(#"rowofthecell %d", rowOfTheCell);
}
If you create a custom UITableViewCell (your own subclass), then you can simply call self in the IBAction. You can link the IBAction function to your button by using storyboard or programmatically when you set up the cell.
- (IBAction)button1Tapped:(id)sender
{
UITableView* tableView = (UITableView *)self.superview;
NSIndexPath* pathOfTheCell = [tableView indexPathForCell:self];
NSInteger rowOfTheCell = pathOfTheCell.row;
NSLog(#"rowofthecell %d", rowOfTheCell);
}
I assume you add buttons to cell in cellForRowAtIndexPath, then what I would do is to create a custom class subclass UIButton, add a tag called rowNumber, and append that data while you adding button to cell.
Another simple way:
Get the point of touch in tableView
Then get index path of cell at point
The index path contains row index
The code is:
- (void)buttonTapped:(id)sender {
UITapGestureRecognizer *tap = (UITapGestureRecognizer *)sender;
CGPoint point = [tap locationInView:theTableView];
NSIndexPath *theIndexPath = [theTableView indexPathForRowAtPoint:point];
NSInteger theRowIndex = theIndexPath.row;
// do your stuff here
// ...
}
Swift 3
Note: This should really go in the accepted answer above, except that meta frowns upon such edits.
#IBAction func doSomething(_ sender: UIButton) {
let buttonPosition = sender.convert(CGPoint(), to: tableView)
let index = tableView.indexPathForRow(at: buttonPosition)
}
Two minor comments:
The default function has sender type as Any, which doesn't have convert.
CGPointZero can be replaced by CGPoint()
One solution could be to check the tag of the button's superview or even higher in the view hierarchy (if the button is in the cell's content view).
I would like to share code in swift -
extension UITableView
{
func indexPathForCellContainingView(view1:UIView?)->NSIndexPath?
{
var view = view1;
while view != nil {
if (view?.isKindOfClass(UITableViewCell) == true)
{
return self.indexPathForCell(view as! UITableViewCell)!
}
else
{
view = view?.superview;
}
}
return nil
}
}
In swift:
#IBAction func buttonAction(_ sender: UIButton) {
guard let indexPath = tableView.indexPathForRow(at: sender.convert(CGPoint(), to: tableView)) else {
return
}
// do something
}

How to use a UITableView to return a value

How would I use a tableView as a value selector?
So I have a series of input fields and what I want is when you select a cetian field it opens a tableview of options that you can pick from as a value for that field.
Upon selecting an option it returns to the previous View with the selected value filling that field.
This is what I do, similar to the Settings > General > International > Language table view in the iPhone/iPod.
The user can tap a row and a check mark will appear. The view is dismissed when "Done" or "Cancel" is tapped.
First, create a UITableViewController that will display your options. Have a toolbar on the top with a Cancel and Done button. Also have these properties:
SEL selector; // will hold the selector to be invoked when the user taps the Done button
id target; // target for the selector
NSUInteger selectedRow; // hold the last selected row
This view will be presented with the presentModalViewController:animated: method so it appears from the bottom of the screen. You could present it in any other way, but it seems kind of standard across iPhone applications.
Before presenting the view, set target and selector so a method will be called when the user taps the "Done" button.
Now, in your newly created UITableViewController you can implement the thetableView:didSelectRowAtIndexPath:` method as:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell * cell = [self.tableView cellForRowAtIndexPath:indexPath];
cell.accessoryType = UITableViewCellAccessoryCheckmark; // show checkmark
[cell setSelected:NO animated:YES]; // deselect row so it doesn't remain selected
cell = [self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:selectedRow inSection:0]];
cell.accessoryType = UITableViewCellAccessoryNone; // remove check from previously selected row
selectedRow = indexPath.row; // remember the newly selected row
}
Also implement cancel and done methods for the toolbar buttons:
- (IBAction)done:(UIBarButtonItem *)item
{
[target performSelector:selector withObject:[stringArray objectAtIndex:selectedRow]];
[self dismissModalViewControllerAnimated:YES];
}
- (IBAction)cancel:(UIBarButtonItem *)item
{
[self dismissModalViewControllerAnimated:YES];
}
You should use UITableViewDelegate's tableView:didSelectRowAtIndexPath:, remember the value somewhere in another object (a share instance/singleton maybe? - depending on your architecture) and then dismiss this table view.
I implemented a ViewController for Date pick.
I create a protocol to return the date picked to the previous view.
#protocol DataViewDelegate
#optional
- (void)dataViewControllerDidFinish:(NSDate*)dateSelected;
#end
...
- (void) viewDidDisappear:(BOOL)animated
{
if ([ (id)(self.delegate) respondsToSelector:#selector(dataViewControllerDidFinish:)])
{
[self.delegate dataViewControllerDidFinish:self.data];
}
[super viewDidDisappear:animated];
}
In the picker view you can use the
tableView:didSelectRowAtIndexPath:
to select the row you want. Here i set the data property.
The previous view is the delegate for the protocol.