How do I, from a cell, get its indexPath in a UITableView?
I've searched around stack overflow and google, but all the information is on the other way around. Is there some way to access the superView/UITableView and then search for the cell?
More information about the context: there are two classes that I have, one is called Cue and one is called CueTableCell (which is a subclass of UITableViewCell) CueTableCell is the visual representation of Cue (both classes have pointers to each other). Cue objects are in a linked list and when the user performs a certain command, the visual representation (the CueTableCell) of the next Cue needs to be selected. So the Cue class calls the select method on the next Cue in the list, which retrieves the UITableView from the cell and calls its selectRowAtIndexPath:animated:scrollPosition:, for which it needs the indexPath of the UITableViewCell.
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
It helps reading the UITableView documentation, even if this is by some regarded to be controversial (see comments below).
The cell has no business knowing what its index path is. The controller should contain any code that manipulates UI elements based on the data model or that modifies data based on UI interactions. (See MVC.)
Try with this from your UITableViewCell:
NSIndexPath *indexPath = [(UITableView *)self.superview indexPathForCell: self];
EDIT: Seems this doesn't work on iOS 8.1.2 and further. Thanks to Chris Prince for pointing it.
Put a weak tableView property in cell's .h file like:
#property (weak,nonatomic)UITableView *tableView;
Assign the property in cellForRowAtIndex method like:
cell.tableView = tableView;
Now wherever you need the indexPath of cell:
NSIndexPath *indexPath = [cell.tableView indexPathForCell:cell];
You can try this simple tip:
on your UITableViewCell Class :
#property NSInteger myCellIndex; //or add directly your indexPath
and on your cellForRowAtIndexPath method :
...
[cell setMyCellIndex : indexPath.row]
now, you can retrieve your cell index anywhere
To address those who say "this is a bad idea", in my case, my need for this is that I have a button on my UITableViewCell that, when pressed, is a segue to another view. Since this is not a selection on the cell itself, [self.tableView indexPathForSelectedRow] does not work.
This leaves me two options:
Store the object that I need to pass into the view in the table cell itself. While this would work, it would defeat the point of me having an NSFetchedResultsController because I do not want to store all the objects in memory, especially if the table is long.
Retrieve the item from the fetch controller using the index path. Yes, it seems ugly that I have to go figure out the NSIndexPath by a hack, but it's ultimately less expensive than storing objects in memory.
indexPathForCell: is the correct method to use, but here's how I would do it (this code is assumed to be implemented in a subclass of UITableViewCell:
// uses the indexPathForCell to return the indexPath for itself
- (NSIndexPath *)getIndexPath {
return [[self getTableView] indexPathForCell:self];
}
// retrieve the table view from self
- (UITableView *)getTableView {
// get the superview of this class, note the camel-case V to differentiate
// from the class' superview property.
UIView *superView = self.superview;
/*
check to see that *superView != nil* (if it is then we've walked up the
entire chain of views without finding a UITableView object) and whether
the superView is a UITableView.
*/
while (superView && ![superView isKindOfClass:[UITableView class]]) {
superView = superView.superview;
}
// if superView != nil, then it means we found the UITableView that contains
// the cell.
if (superView) {
// cast the object and return
return (UITableView *)superView;
}
// we did not find any UITableView
return nil;
}
P.S. My real code does access all this from the table view, but I'm giving an example of why someone might want to do something like this in the table cell directly.
For swift
let indexPath :NSIndexPath? = (self.superview.superview as! UITableView)?.indexPathForCell(self)
The answer to this question actually helped me a lot.
I used NSIndexPath *indexPath = [self.tableView indexPathForCell:sender];
The sender in my case was a UITableViewCell and comes from the prepareForSegue method.
I used this because I did not have a TableViewControllerbut i had UITableView property outlet
I needed to find out the title of the Cell and hence needed to know the indexPath of it.
Hope this helps anyone!
On iOS 11.0, you can use UITableView.indexPathForRow(at point: CGPoint) -> IndexPath?:
if let indexPath = tableView.indexPathForRow(at: cell.center) {
tableView.selectRow
(at: indexPath, animated: true, scrollPosition: .middle)
}
It gets the center point of the cell and then the tableView returns the indexPath corresponding to that point.
try this (it only works if the tableView has only one section and all cells has equal heights) :
//get current cell rectangle relative to its superview
CGRect r = [self convertRect:self.frame toView:self.superview];
//get cell index
int index = r.origin.y / r.size.height;
Swift 3.0 and above-
If one needs to get the indexpath from within a custom cell-
if let tableView = self.superview as? UITableView{
if let indexPath = tableView.indexPath(for: self){
print("Indexpath acquired.")
}else{
print("Indexpath could not be acquired from tableview.")
}
}else{
print("Superview couldn't be cast as tableview")
}
It's good practice is to look out for the failure cases.
Try with this from your UITableViewCell:
NSIndexPath *indexPath = [(UITableView *)self.superview.superview indexPathForCell:self];
Swift 4.x
To provide access to my cell, i did something like this:
I have an extension of the UIView:
extension UIView {
var parentViewController: UIViewController? {
var parentResponder: UIResponder? = self
while parentResponder != nil {
parentResponder = parentResponder!.next
if let viewController = parentResponder as? UIViewController {
return viewController
}
}
return nil
}
}
And then.. in my cell Class:
class SomeCell: UITableViewCell {
func someMethod() {
if let myViewController = self.parentViewController as? CarteleraTableViewController {
let indexPath : IndexPath = (myViewController.tableView).indexPath(for: self)!
print(indexPath)
}
}
}
Thats all form me.
Best regards.
Related
I have a UITableView of 'people' for my iOS app. I have created an 'add' screen to add new people to my persistent store, which works perfectly.
This add view uses a form created with a UITableView and subclassed UITableViewCells, with a UITextField and UILabel, which although works well, I am very new to iOS programming and feel that this may not be the most efficient way.
I am trying to re-use this add view to be my detail, add, and edit view, and I can successfully set a 'Person' entity as a property in the detail view. I have the following code in my viewDidLoad method, where the adding property is set in the prepareForSegue method in the previous ViewController :
if (self.adding)
{
self.editing = YES;
self.title = #"Add Person";
}
else
{
self.title = self.person.name;
[self setDetail];
}
My problem is that when I try to pre-populate my detail view's fields (my setDetail method), I am unable to set my UITextField text with the name property from my person entity. Here's the code I'm using to retrieve the UITextField and set it's text property with:
form is the UITableView;
UITableViewCell *nameCell = [form cellForRowAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
UITextField *nameField = (UITextField *)[nameCell viewWithTag:100];
nameField.text = self.person.name;
If I NSLog nameCell it returns (null)
I hope that's enough explanation. Any pointers would help a lot!
Instead of setting in viewDidLoad:, do it in your table view data source methods. i.e
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// Deque the nameCell and prepare the cell if its not available.
UITextField *nameField = (UITextField *)[nameCell viewWithTag:100];
nameField.text = self.person.name;
return nameCell.
}
I don't really understand your question, but cellForRowAtIndexPath may return nil because
Return Value
An object representing a cell of the table or nil if the cell is not visible or indexPath is out of range.
Official Document: cellForRowAtIndexPath:
I have a UITableViewCell which is linked to an object and I need to tell if the cell is visible. From the research I've done, this means I need to somehow access the UITableView that contains it (from there, there are several ways to check if it's visible). So I'm wondering if UITableViewCell has a pointer to the UITableView, or if there was any other way to get a pointer from the cell?
To avoid checking the iOS version, iteratively walk up the superviews from the cell's view until a UITableView is found:
Objective-C
id view = [cellInstance superview];
while (view && [view isKindOfClass:[UITableView class]] == NO) {
view = [view superview];
}
UITableView *tableView = (UITableView *)view;
Swift
var view = cellInstance.superview
while (view != nil && (view as? UITableView) == nil) {
view = view?.superview
}
if let tableView = view as? UITableView {
tableView.beginUpdates()
tableView.endUpdates()
}
In iOS7 beta 5 UITableViewWrapperView is the superview of a UITableViewCell. Also UITableView is superview of a UITableViewWrapperView.
So for iOS 7 the solution is
UITableView *tableView = (UITableView *)cell.superview.superview;
So for iOSes up to iOS 6 the solution is
UITableView *tableView = (UITableView *)cell.superview;
Swift 5 extension
Recursively
extension UIView {
func parentView<T: UIView>(of type: T.Type) -> T? {
guard let view = superview else {
return nil
}
return (view as? T) ?? view.parentView(of: T.self)
}
}
extension UITableViewCell {
var tableView: UITableView? {
return parentView(of: UITableView.self)
}
}
Using loop
extension UITableViewCell {
var tableView: UITableView? {
var view = superview
while let v = view, v.isKind(of: UITableView.self) == false {
view = v.superview
}
return view as? UITableView
}
}
Before iOS7, the cell's superview was the UITableView that contained it. As of iOS7 GM (so presumably will be in the public release as well) the cell's superview is a UITableViewWrapperView with its superview being the UITableView. There are two solutions to the problem.
Solution #1: Create a UITableViewCell category
#implementation UITableViewCell (RelatedTable)
- (UITableView *)relatedTable
{
if ([self.superview isKindOfClass:[UITableView class]])
return (UITableView *)self.superview;
else if ([self.superview.superview isKindOfClass:[UITableView class]])
return (UITableView *)self.superview.superview;
else
{
NSAssert(NO, #"UITableView shall always be found.");
return nil;
}
}
#end
This is a good drop-in replacement to using cell.superview, makes it easy to refactor your existing code -- just search and replace with [cell relatedTable], and throw in an assert to ensure that if the view hierarchy changes or reverts in the future it will show up immediately in your tests.
Solution #2: Add a Weak UITableView reference to UITableViewCell
#interface SOUITableViewCell
#property (weak, nonatomic) UITableView *tableView;
#end
This is a much better design, though it will require a bit more code refactoring to use in existing projects. In your tableView:cellForRowAtIndexPath use SOUITableViewCell as your cell class or make sure your custom cell class is subclassed from SOUITableViewCell and assign the tableView to the cell's tableView property. Inside the cell you can then refer to the containing tableview using self.tableView.
If it is visible then it has a superview. And ... surprise ... the superview is an UITableView object.
However, having a superview is no guarantee for being on screen. But UITableView provides methods to determine which cells are visible.
And no, there is no dedicated reference from a cell to a table. But when you subclass UITableViewCell you may introduce one and set it upon creation. (I did that myself a lot before I thought of the subview hierarchy.)
Update for iOS7:
Apple has changed the subview hierarchy here. As usual when working with things that are not detailled documented, there is always a risk that things change. It is far saver to "crawl up" the view hierarchy until a UITableView object is eventually found.
Swift 2.2 solution.
An extension for UIView that recursively searches for a view with a specific type.
import UIKit
extension UIView {
func lookForSuperviewOfType<T: UIView>(type: T.Type) -> T? {
guard let view = self.superview as? T else {
return self.superview?.lookForSuperviewOfType(type)
}
return view
}
}
or more compact (thanks to kabiroberai):
import UIKit
extension UIView {
func lookForSuperviewOfType<T: UIView>(type: T.Type) -> T? {
return superview as? T ?? superview?.superviewOfType(type)
}
}
In your cell you just call it:
let tableView = self.lookForSuperviewOfType(UITableView)
// Here we go
Mind that UITableViewCell is added on the UITableView only after cellForRowAtIndexPath execution.
Whatever you may end up managing to do by calling super view or via the responder chain is going to be very fragile.
The best way to do this, if the cells wants to know something, is to pass an object to the cell that responds to some method that answers the question the cell wants to ask, and have the controller implement the logic of determining what to answer (from your question I guess the cell wants to know if something is visible or not).
Create a delegate protocol in the cell, set the delegate of the cell the tableViewController and move all the ui "controlling" logic in the tableViewCotroller.
The table view cells should be dum view that will only display information.
I created a category on UITableViewCell to get its parent tableView:
#implementation UITableViewCell (ParentTableView)
- (UITableView *)parentTableView {
UITableView *tableView = nil;
UIView *view = self;
while(view != nil) {
if([view isKindOfClass:[UITableView class]]) {
tableView = (UITableView *)view;
break;
}
view = [view superview];
}
return tableView;
}
#end
Best,
Here is the Swift version based on above answers. I have generalized into ExtendedCell for later usage.
import Foundation
import UIKit
class ExtendedCell: UITableViewCell {
weak var _tableView: UITableView!
func rowIndex() -> Int {
if _tableView == nil {
_tableView = tableView()
}
return _tableView.indexPathForSelectedRow!.row
}
func tableView() -> UITableView! {
if _tableView != nil {
return _tableView
}
var view = self.superview
while view != nil && !(view?.isKindOfClass(UITableView))! {
view = view?.superview
}
self._tableView = view as! UITableView
return _tableView
}
}
Hope this help :)
I based this solution on Gabe's suggestion that UITableViewWrapperView object is the superview of UITableViewCell object in iOS7 beta5 .
Subclass UITableviewCell :
- (UITableView *)superTableView
{
return (UITableView *)[self findTableView:self];
}
- (UIView *)findTableView:(UIView *)view
{
if (view.superview && [view.superview isKindOfClass:[UITableView class]]) {
return view.superview;
}
return [self findTableView:view.superview];
}
I Borrowed and modified a little bit from the above answer and come up with the following snippet.
- (id)recursivelyFindSuperViewWithClass:(Class)clazz fromView:(id)containedView {
id containingView = [containedView superview];
while (containingView && ![containingView isKindOfClass:[clazz class]]) {
containingView = [containingView superview];
}
return containingView;
}
Passing in class offers the flexibility for traversing and getting views other than UITableView in some other occasions.
My solution to this problem is somewhat similar to other solutions, but uses an elegant for-loop and is short. It should also be future-proof:
- (UITableView *)tableView
{
UIView *view;
for (view = self.superview; ![view isKindOfClass:UITableView.class]; view = view.superview);
return (UITableView *)view;
}
UITableView *tv = (UITableView *) self.superview.superview;
BuyListController *vc = (BuyListController *) tv.dataSource;
Instead of superview, try using ["UItableViewvariable" visibleCells].
I used that in a foreach loops to loop through the cells that the app saw and it worked.
for (UITableView *v in [orderItemTableView visibleCells])//visibleCell is the fix.
{
#try{
[orderItemTableView reloadData];
if ([v isKindOfClass:[UIView class]]) {
ReviewOrderTableViewCell *cell = (ReviewOrderTableViewCell *)v;
if (([[cell deleteRecord] intValue] == 1) || ([[[cell editQuantityText] text] intValue] == 0))
//code here
}
}
}
Works like a charm.
Minimally tested but this non-generic Swift 3 example seems to work:
extension UITableViewCell {
func tableView() -> UITableView? {
var currentView: UIView = self
while let superView = currentView.superview {
if superView is UITableView {
return (superView as! UITableView)
}
currentView = superView
}
return nil
}
}
this code `UITableView *tblView=[cell superview]; will give you an instance of the UItableview which contains the tabe view cell
I suggest you traverse the view hierarchy this way to find the parent UITableView:
- (UITableView *) findParentTableView:(UITableViewCell *) cell
{
UIView *view = cell;
while ( view && ![view isKindOfClass:[UITableView class]] )
{
#ifdef DEBUG
NSLog( #"%#", [[view class ] description] );
#endif
view = [view superview];
}
return ( (UITableView *) view );
}
Otherwise your code will break when Apple changes the view hierarchy again.
Another answer that also traverses the hierarchy is recursive.
UITableViewCell Internal View Hierarchy Change in iOS 7
Using iOS 6.1 SDK
<UITableViewCell>
| <UITableViewCellContentView>
| | <UILabel>
Using iOS 7 SDK
<UITableViewCell>
| <UITableViewCellScrollView>
| | <UIButton>
| | | <UIImageView>
| | <UITableViewCellContentView>
| | | <UILabel>
The new private UITableViewCellScrollView class is a subclass of UIScrollView and is what allows this interaction:
![enter image description here][1]
[1]: http://i.stack.imgur.com/C2uJa.gif
http://www.curiousfind.com/blog/646
Thank You
You can get it with one line of code.
UITableView *tableView = (UITableView *)[[cell superview] superview];
extension UIView {
func parentTableView() -> UITableView? {
var viewOrNil: UIView? = self
while let view = viewOrNil {
if let tableView = view as? UITableView {
return tableView
}
viewOrNil = view.superview
}
return nil
}
}
from #idris answer
I wrote an expansion for UITableViewCell in Swift
extension UITableViewCell {
func relatedTableView() -> UITableView? {
var view = self.superview
while view != nil && !(view is UITableView) {
view = view?.superview
}
guard let tableView = view as? UITableView else { return nil }
return tableView
}
So I have looked around a quite a bit, and nothing on here seems to explain exactly the correct way of doing this. I have 7 UITextFields within a custom UITableViewCell.
My question is this: What is the correct way of managing the delegate of these UITextFields?
Since the custom cells are technically part of the "model" portion of the project, I would rather have the controller that controls the UITableView also control the text fields in the table's cells, but I can not figure out how to set the delegate for the text fields (which are created in the subclass of UITableViewCell) to this view controller.
Would it be bad practice to just make the subclass of UITableViewCell conform to UITextField delegate and manage all of that stuff in there? If so, how else should I go about doing this?
Thanks, any help would be appreciated.
You shouldn't have a problem setting the delegate of the cell's text field to be your view controller.
This is what you need to do:
1) The view controller needs to implement the UITextFieldDelegate protocol
2) Declare a property for the text field in your custom cell
#property (nonatomic, retain) IBOutlet UITextField *textField;
3) Then set the view controller as the text field's delegate in the method cellForRowAtIndexPath
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"Cell";
MyCustomCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil)
{
// use this if you created your cell with IB
cell = [[[NSBundle mainBundle] loadNibNamed:#"MyCustomCell" owner:self options:nil] objectAtIndex:0];
// otherwise use this
cell = [[[MyCustomCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
// now set the view controller as the text field delegate
cell.textField.delegate = self;
}
// configure cell...
return cell;
}
In my opinion the cell should manage the keyboard since it is the one that is holding the UITextField. You can set your cell as the UITextField delegate. In my own application, I have done this and then made my cell have it's own delegate. Any of the methods of UITextField or any new methods that should be handled by the controller can be passed along to the controller through the cells delegate.
In this way the cell can still be generic without knowing anything about what the application is actually doing.
My suggestion would be to "tag" (i.e. set the tag) of each textField with a value that encodes the section, row, and one-of-7 text views in the table, then make the UIViewController the delegate.
So you need to bound the size of these - say you will never have more than 100 rows. So you encode this as:
.tag = 1000*section + 100*row +
When you get a message you can have a method/function take the tag and decode it into section, row, tag, and do what you need to have done.
To declare your TableViewController as the delegate include <UITextFieldDelegate> at the end of your #interface in the TableViewController's .h file.
#interface MyTableViewController : UITableViewController <UITextFieldDelegate>
Then, connect the text fields by ctrl-dragging each field under the #interface. Each UITextField is connected to its respective property by an IBOutlet. Finally, in the .m file include the following function to show the delegate which field you want to return....
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
[aTextField resignFirstResponder];
return YES;
}
Swift version (Based on Eyal's answer)
class MyViewController: UIViewController, ... , UITextFieldDelegate {
#IBOutlet var activeTextField: UITextField! //doesn't need to connect to the outlet of textfield in storyboard
....
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
....
var cellTextField = self.view.viewWithTag(101) as? UITextField
cellTextField!.delegate = self;
....
}
I have a tableView, and in that i have a UISegmentController. So each cell will have a UISegmentController each.
When the user clicks on a Segment in the UISegmentController (of a given cell), how do i know which cell was clicked? and i need to NSLog the title of that cell, how can i do this (note: the user will only be clicking on the UISegmentController of the cell, and not the cell it self)
The following code is the method that will be called when the UISegmentController is clicked;
-(void)segmentOfCellPressed:(id)sender{
}
Try this:
-(void)segmentOfCellPressed:(id)sender{
UISegmentController *segmentController = (UISegmentController *)sender;
YourCellClass *cell=(YourCellClass *)segmentController.superview.superview; // your clicked cell
// or a little bit more verbose but imho easier to understand:
// UIView *contentView = [segmentController superview];
// YourCellClass *cell = (YourCellClass *)[contentView superview];
NSIndexPath *path = [yourTableView indexPathForCell:cell]; //indexPath of clicked cell
}
Should be pretty easy. UISegmentedController derives from UIControl which derives UIView. UIView objects have a tag property. When you create each UISegmentedControler, give each one a unique tag value. You will need to manage which tag values separately in a dictionary perhaps.
-(void)segmentOfCellPressed:(id)sender
{
UISegmentedController *segmentedController = (UISegmentedController *)sender;
UITableViewCell *cell = [self cellFromTag:segmentedController.tag];
}
You will need to create a method called cellFromTag that will return the cell from the tag value of the UISegmentedController. If you don't want the cell but something else, then you can return that instead.
You could set the tag property of the segment controller to be the index of the cell in the cellForRowAtIndexPath then in the selector you could query the segment controller and then get the cell by the tag:
UISegmentController *segmentController = (UISegmentController*)sender;
int row = segmentController.tag;
Hopefully that helps.
In the cellForRowAtIndexPath method where you add the UISegmentController to the cell do the following:
segment.tag = indexPath.row;
So I have a detail view that need to display information by getting a index integer caculated from the indexpath from the selected cell in self.tableview, I've been using the NSFetchedResultsController for the self.tableview too. I've also implemented a UISearchDisplayController to do search.
Question:
How do I convert the selected indexPath in the UISearchDisplayController tableview to the indexpath of the original self.tableview? Or do I need to set up a NSArray instance and loop through it to find out the index? What's the most efficient way to do it?
Here is the code:
-(void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath {
NSLog(#"display project details");
if (tableView == self.table) {
[parentController projectSelectedFromList:indexPath.row];
}else{
NSInteger index;
//what to put here? To get the indexPath of the self.table from search tableview
[parentController projectSelectedFromList:index];
}
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
Assuming no duplicates,
id selectedObject = [searchArray objectAtIndex:indexPath.row];
index = [sourceArray indexOfObject:selectedObject];
Idea is pretty simple. Get the selected object of your search array as it will map directly to the index path's row. Then search for the object's index within the source or master array. This should give you the index you want.
Look at what you have you got in tableView:cellForRowAtIndexPath: of your tableView data source (usually the tableViewController object). Assuming you do a similar check on whether you are using the standard tableView or the search tableView there - and if you're not, then you're search filter surely won't be effective - you just need to have analogous code in the your tableView:didSelectRowAtIndexPath:.
If I'm missing the point here then apologies... and you might need to say a bit more in your question.
I found out the solution:
-(void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath {
NSLog(#"display project details");
if (tableView == self.table) {
[parentController projectSelectedFromList:indexPath.row];
NSLog(#"indexpath at orignal tableview is: %#", [indexPath description]);
}else{
NSIndexPath *indexPathForOriginal = [resultsController indexPathForObject: [self.filteredResults objectAtIndex:indexPath.row]];
NSInteger index = indexPathForOriginal.row;
[parentController projectSelectedFromList:index];
NSLog(#"indexpath at search tableview is: %#", [indexPathForOriginal description]);
}
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
Swift version of #Deepak Danduprolu answer:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
var selectedObject = searchedArray[indexPath.row]
var index = OrignalArray.index(of: selectedObject)
print("index", index) // This will give you index of selected array from the original array
}