How to get UITableView from UITableViewCell? - iphone

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
}

Related

IOS 7 - How to get the indexPath from button placed in UITableViewCell

I've programmatically created a UITableView and added UISwitch to it's cell accessory view.
This is my code for UISwitch in cell accessory view in cellForRowAtIndexPath method.
UISwitch *accessorySwitch = [[UISwitch alloc]initWithFrame:CGRectZero];
[accessorySwitch setOn:NO animated:YES];
[accessorySwitch addTarget:self action:#selector(changeSwitch:) forControlEvents:UIControlEventValueChanged];
cell.accessoryView = accessorySwitch;
This is the method which is called after the button is clicked.
- (void)changeSwitch:(UISwitch *)sender{
UITableViewCell *cell = (UITableViewCell *)[sender superview];
NSIndexPath *indexPath = [self.filterTableView indexPathForCell:cell];
NSLog(#"%ld",(long)indexPath);
……………. My other code…….
}
I am able to print the index path value in iOS 6
But in iOS 7 it prints nil,
Am i missing anything in iOS 7 or there is some other approach to get the indexPath in iOS 7
Thanks,
Arun.
NSLog(#"%ld",(long)indexPath); is wrong this will print the pointer address of indexPath
try using following codes
CGPoint center= sender.center;
CGPoint rootViewPoint = [sender.superview convertPoint:center toView:self.filterTableView];
NSIndexPath *indexPath = [self.filterTableView indexPathForRowAtPoint:rootViewPoint];
NSLog(#"%#",indexPath);
Swift solution:
A UITableView extension like this one can be useful for this.
extension UITableView {
func indexPathForView(view: AnyObject) -> NSIndexPath? {
let originInTableView = self.convertPoint(CGPointZero, fromView: (view as! UIView))
return self.indexPathForRowAtPoint(originInTableView)
}
}
Usage becomes easy everywhere.
let indexPath = tableView.indexPathForView(button)
If you have button in cell. You can get cell by calling superview. And then can get Indexpath by this way.
(void)obButtonTap:(UIButton *)sender {
UITableViewCell *cell = (UITableViewCell *)sender.superview;
NSIndexPath *indexPath = [tableView indexPathForCell:cell];
}
You can assign tags to each switch in cellForRowAtIndexPath method like
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UISwitch *accessorySwitch = [[UISwitch alloc]initWithFrame:CGRectZero];
[accessorySwitch setOn:NO animated:YES];
[accessorySwitch addTarget:self action:#selector(changeSwitch:) forControlEvents:UIControlEventValueChanged];
cell.accessoryView = accessorySwitch;
accessorySwitch.tag = indexPath.row;
}
- (void)changeSwitch:(UISwitch *)sender{
NSIndexPath *indexPath = [NSIndexPath indexPathWithIndex:[sender tag]];
NSLog(#"%ld",(long)indexPath);
}
SWIFT 4 :
extension UITableView {
func indexPathForView(view: AnyObject) -> NSIndexPath? {
let originInTableView = self.convert(CGPoint.zero, from: (view as! UIView))
return self.indexPathForRow(at: originInTableView)! as NSIndexPath
}
}
SWIFT 4 Conversion, creates global extension:
extension UITableView {
func indexPathForView(view: UIView) -> IndexPath? {
let originInTableView = self.convert(CGPoint.zero, from: (view))
return self.indexPathForRow(at: originInTableView)
}
}
Usage example:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = ...
cell.myButton.addTarget(self, action: #selector(myButtonAction(_:)), for: .primaryActionTriggered)
return cell
}
#objc func myButtonAction(_ button:UIButton) {
guard let ip = self.tableView.indexPathForView(view: button) else {
return
}
}
You are probably getting burned, making the assumption that the switch's superview is going to be the tableViewCell. Maybe in iOS 7 they changed the hierarchy to achieve some sort of visual effect; maybe there is a new view inserted in there.
You could possibly subclass UISwitch and give it an indexPath property. Then in your cellForRowAtIndexPath, assign it there so you have a reference.
Tested on Swift5.x
if you want to get row number when you click on the button placed in tableViewCell, Then the below function will work.
#IBAction func buttonClickeAction(_ sender: UIButton) {
let btnPoint = sender.convert(CGPoint.zero, to: self.tableView)
let indexPath = self.tableView.indexPathForRow(at: btnPoint)
print(indexPath?.row)
}
I think that is dangerous to rely on the position of the button to know which one have been clicked.
I prefere create a dictionary taking as a key the button/switch itself, and as a value the indexpath :
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
...
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
NSValue * accessorySwitchKey = [NSValue valueWithNonretainedObject:[cell settingSwitch]];
[[self switchIndexDictionary]setObject:indexPath accessorySwitchKey];
...
}
then when the switch/button is triggered I get the indexpath easily from my dictionary :
- (void)toggleSetting:(id)sender
{
UISwitch *selectedSwitch = (UISwitch *) sender;
NSValue * accessorySwitchKey = [NSValue valueWithNonretainedObject:selectedSwitch];
NSIndexPath *indexPath = [[self switchIndexDictionary]objectForKey: accessorySwitchKey];
}

How to get UITableViewCell indexPath from the Cell?

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.

How to get cell indexpath in uitextfield Delegate Methods?

I have two textfields in a custom cell how to get the indexpath value of Tableview cell in textfield delegate methods I want to get the input value from user and save it to the relavent object. the user can add more cells by clicking button(Add More) in cell..
Thanks in Advance...
Update to iOS7!
With new features in iOS7 now code should be :
UITableViewCell *textFieldRowCell;
if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_6_1) {
// Load resources for iOS 6.1 or earlier
textFieldRowCell = (UITableViewCell *) textField.superview.superview;
} else {
// Load resources for iOS 7 or later
textFieldRowCell = (UITableViewCell *) textField.superview.superview.superview;
// TextField -> UITableVieCellContentView -> (in iOS 7!)ScrollView -> Whoola!
}
NSIndexPath *indexPath = [self.tableView indexPathForCell:textFieldRowCell];
A more dynamic solution (no hardcoded superview levels and same code for different iOS versions).
Further, indexPathForCell: will not work if the cell is not visible, therefore I use indexPathForRowAtPoint: as workaround.
//find the UITableViewcell superview
UIView *cell = textField;
while (cell && ![cell isKindOfClass:[UITableViewCell class]])
cell = cell.superview;
//use the UITableViewcell superview to get the NSIndexPath
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:cell.center];
This is how I have been doing it and have been having better luck. I grab the origin of the textField frame. Convert that to a point. Then convert the point to an index path.
-(void)textFieldDidBeginEditing:(UITextField *)textField
{
CGPoint origin = textField.frame.origin;
CGPoint point = [textField.superview convertPoint:origin toView:self.tableView];
NSIndexPath * indexPath = [self.tableView indexPathForRowAtPoint:point];
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionMiddle animated:YES];
}
Try this method to get textfield dynamically anywhere from your tableview controller
#pragma mark - Get textfield indexpath
- (NSIndexPath *)TextFieldIndexpath:(UITextField *)textField
{
CGPoint point = [textField.superview convertPoint:textField.frame.origin toView:self.TblView];
NSIndexPath * indexPath = [self.TblView indexPathForRowAtPoint:point];
NSLog(#"Indexpath = %#", indexPath);
return indexPath;
}
To get The indexPath try the following code.
UIView *contentView = (UIVIew *)[textfield superview];
UITableViewCell *cell = (UITableViewCell *)[contentView superview];
if(IS_IOS7_OR_GREATER) {
cell = (UITableViewCell *)[[contentView superview] superview];
}
NSIndexPath *indexPath = [self.tableview indexPathForCell:cell];
Tats it you are done.
To be simple,
NSIndexPath *indexPath = [self.tableview indexPathForCell:(UITableViewCell *)[(UIVIew *)[textfield superview] superview]];
if(IS_IOS7_OR_GREATER) {
cell = (UITableViewCell *)[[[textfield superview] superview] superview];
}
Check the updated answer.
set cell indexpath value to UITextField tag property and you can access the indexpath in delegate methods like textfield.tag
You can set the tags of textfields in cellForRowAtIndexPath: such that it stores info of both cell and text field
For example : If it is cell in 4th row, tag of 1st and 2nd textfields can be 41 and 42 respectively. Similarly, tags of textfields should be 51 and 52 for 5th row and so on...
Then in textfield delegate method, you can get textfield.tag to identify active textfield.
This can be done in the Objective-C runtime for any instance (doesn't have to be UITextField), with any associated object (doesn't have to be NSIndexPath).
For this question, we could create a category UIView+RepresentingIndexPath.
Our interface allows us to set and retrieve an NSIndexPath:
#interface UIView (RepresentingIndexPath)
- (void)representIndexPath:(NSIndexPath *)indexPath;
- (NSIndexPath *)representedIndexPath;
#end
Our implementation uses Objective-C associated objects to set and retrieve an index path on a view:
#import "UIView+RepresentingIndexPath.h"
#import <objc/runtime.h>
static char IndexPathKey;
#implementation UIView (RepresentingIndexPath)
- (void)representIndexPath:(NSIndexPath *)indexPath
{
objc_setAssociatedObject(self, &IndexPathKey, indexPath, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSIndexPath *)representedIndexPath
{
return objc_getAssociatedObject(self, &IndexPathKey);
}
#end
In action:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
TextFieldTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"TextFieldCell" forIndexPath:indexPath];
[cell.textField addTarget:self action:#selector(textFieldTextChanged:) forControlEvents:UIControlEventEditingChanged];
[cell.textField representIndexPath:indexPath];
return cell;
}
- (void)textFieldTextChanged:(UITextField *)sender
{
NSIndexPath *indexPath = [sender representedIndexPath];
NSLog(#"%#", indexPath);
}
💣
One final note! Messing around in the runtime should really be avoided if you can achieve what you're trying to do without doing so. Just thought I'd add another solution!
I find this answer searching how can I find the index path of a cell with inside a UITextField.
So, thanks to the answer above, I put my code here, hoping might be usefull.
- (void)searchSelectedIndexPath:(UIView*)view {
// This allow to find selected index path for a table view cell with a text field inside.
for (UIView* subview in view.subviews) {
if ([view isKindOfClass:[UITextField class]]) {
if ([view isFirstResponder]) {
UIView *cell = view;
while (cell && ![cell isKindOfClass:[UITableViewCell class]]) {
cell = cell.superview;
}
self.selectedIndexPath = [self.tableView indexPathForRowAtPoint:cell.center];
return;
}
}
[self searchSelectedIndexPath:subview];
}
}
In this way, when keyboard notification will be raise:
- (void)keyboardDidShow:(NSNotification*)notification {
[self searchSelectedIndexPath:self.tableView];
}
In case somebody like me needs #Kerkness' answer (this one really worked for me) in swift:
func textFieldDidBeginEditing(_ textField: UITextField) {
let origin: CGPoint = textField.frame.origin
let point: CGPoint? = textField.superview?.convert(origin, to: tableView)
let indexPath: IndexPath? = tableView.indexPathForRow(at: point ?? CGPoint.zero)
tableView.scrollToRow(at: indexPath!, at: .middle, animated: true)
}
It should be straight forward enough: you get the point then you get the indexPath and do whatever you need with it!
Thanks, #Luka, it works in a great way.
Here is the swift 4 solution,
var selectedIndexPath: IndexPath?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShowHide(_ :)), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShowHide(_ :)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}
func searchSelectedIndexPath(view: UIView) {
view.subviews.forEach { (subview) in
if view is UITextView, view.isFirstResponder == true {
var cell:UIView? = view;
while cell != nil && !(cell is UITableViewCell) {
cell = cell?.superview;
}
if cell != nil {
self.selectedIndexPath = self.dashBoardTableView.indexPathForRow(at: (cell?.center)!)
return
}
}
self.searchSelectedIndexPath(view: subview)
}
}
// Keyboard notification observer menthod
#objc fileprivate func keyboardWillShowHide(_ notification: NSNotification){
if notification.name == NSNotification.Name.UIKeyboardWillShow {
UIView.animate(withDuration: duration, animations: { () -> Void in
self.selectedIndexPath = nil;
self.searchSelectedIndexPath(view: self.tableView)
if let indexpath = self.selectedIndexPath {
self.tableView.scrollToRow(at: indexpath, at: .top, animated: false)
} else{
self.bottomContriant.constant = keyboardHeight
self.view.layoutSubviews()
}
})
} else {
self.bottomContriant.constant = 15
UIView.animate(withDuration: duration, animations: { () -> Void in
self.view.layoutIfNeeded()
})
}
}

Reference to the parent tableView of a cell

Is there a way to have a reference of the parent tableView from the tableview's cell?
Thanks!
You can add this method to your custom subclass of UITableViewCell:
- (id)parentTableView {
UIView *v = [self superview];
UIView *previous = nil;
while (v && ![v isKindOfClass:[UITableView class]] && v != previous) {
previous = v;
v = [v superview];
}
return v == previous ? nil : v;
}
If you're not subclassing UITableViewCell, just replace self in the code above with your reference to a UITableViewCell.
If you are accessing the cell through the didSelectRowATIndexPath: you can get it easily as
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// here tableView is the one you want.
}

dequeueReusableCellWithIdentifier returns wrong cell

I have a UITableView that uses custom cells, loaded from nibs, and hooked up to a controller (using initWithNibName:bundle:). Basically, I've noticed that dequeueReusableCellWithIdentifier returns a cell that has already been loaded, but when it shouldn't.
Here are my delegate methods:
- (UITableViewCell *) tableView: (UITableView *) tv cellForRowAtIndexPath: (NSIndexPath *) indexPath {
return [[self controllerForTableView: tv cellForRowAtIndexPath: indexPath] cell];
}
- (TableViewCellBaseController *) controllerForTableView: (UITableView *) tv cellForRowAtIndexPath: (NSIndexPath *) indexPath {
[self checkValidTableView: tv];
UIViewController *controller;
Class class;
Event *event;
int row = [indexPath row];
DLOG(#"at index path row: %i", row);
if (row == [currentEvents count]) {
controller = [self tableCellContainerFromNibName: nibName
tableView: tv
atIndex: row
withClass: class];
} else {
class = [EventFeaturedTableViewCell class]; // TODO: OR class = [EventNonFeaturedTableViewCell class];
event = [self findEventFromIndex: row];
lastSelectedEvent = event;
DLOG(#"event: %#", event);
controller = [self tableCellContainerFromNibName: NSStringFromClass(class)
tableView: tv
atIndex: row
withClass: class
perform: #selector(initTableCellWithController:)
on: self];
}
return controller;
}
- (TableViewCellBaseController *) tableCellContainerFromNibName: (NSString *) nibName
tableView: (UITableView *) tableView
atIndex: (int) index
withClass: (Class) class
perform: (SEL) selector
on: obj {
CustomTableViewCell *cell = (CustomTableViewCell *) [tableView dequeueReusableCellWithIdentifier: nibName];
TableViewCellBaseController *controller;
DLOG(#"Cell: %#", cell);
DLOG(#"Cell Identifier: %#", nibName);
if (cell == nil) {
controller = [[class alloc] initWithNibName: nibName bundle: nil];
if (obj) {
[obj performSelector: selector withObject: controller];
}
// run this after setting data as controller.view eager loads the view
cell = controller.cell = controller.view;
cell.controller = controller;
} else {
controller = cell.controller;
if (obj) {
[obj performSelector: selector withObject: controller];
}
}
return controller;
}
- (void) initTableCellWithController: (EventsIndexTableViewCell *) controller {
controller.event = lastSelectedEvent;
}
The Custom Cells have been hooked up in IB to the controller using the controller's view property as well as a 'cell' property. The Custom Cells have a unique identifier set directly in the nib.
I've already verified that the data in the controller is correct.
Here's the effect I'm seeing in the simulator:
http://screencast.com/t/NI2Tpc7GKEi
Notice that the event 'Atlantic Bay' shows up first in the table, then later on in the table. When scrolling back up, it no longer shows up as the first entry in the table!
To continue viggio24's line, I agree that I don't see a place that you're properly reconfiguring the cell. When you -dequeue... you're going to get a cell that already has "stuff" in it. It's your job to reset everything visible.
You should also read carefully Loading Custom Table-View Cells From Nib Files. Typically you use -loadNibNamed:owner:options: for this, rather than -initWithNibName:bundle:.