Reference from UITableViewCell to parent UITableView?

IphoneObjective CCocoa Touch

Iphone Problem Overview


Is there any way to access the owning UITableView from within a UITableViewCell?

Iphone Solutions


Solution 1 - Iphone

Store a weak reference to the tableView in the cell, which you'd set in -tableView:cellForRowAtIndexPath: of your table's dataSource.

This is better than relying on self.superview to always be exactly the tableView is fragile. Who knows how Apple might re-organize the view hierarchy of UITableView in the future.

Solution 2 - Iphone

Here's a nicer way to do it, which does not rely on any particular UITableView hierarchy. It will work with any future iOS version, provided that UITableView does not change classname altogether. Not only this is extremely unlikely, but if it does happen you will have to retouch your code anyway.

Just import the category below and get your reference with [myCell parentTableView]

@implementation UIView (FindUITableView)

-(UITableView *) parentTableView {
    // iterate up the view hierarchy to find the table containing this cell/view
    UIView *aView = self.superview;
    while(aView != nil) {
        if([aView isKindOfClass:[UITableView class]]) {
            return (UITableView *)aView;
        }
        aView = aView.superview;
    }
    return nil; // this view is not within a tableView
}

@end


// To use it, just import the category and invoke it like so:
UITableView *myTable = [myTableCell parentTableView];

// It can also be used from any subview within a cell, from example
// if you have a UILabel within your cell, you can also do:
UITableView *myTable = [myCellLabel parentTableView];

// NOTE:
// If you invoke this on a cell that is not part of a UITableView yet
// (i.e., on a cell that you just created with [[MyCell alloc] init]),
// then you will obviously get nil in return. You need to invoke this on cells/subviews
// that are already part of a UITableView.


UPDATE
There is some discussion in the comments about whether keeping a weak reference is a better approach. It depends on your circumstances. Traversing the view hierarchy has some small runtime penalty as you are looping until the target UIView is identified. How deep are your views? On the other hand, keeping a reference on every cell has a minimal memory penalty (a weak reference is a pointer after all), and generally adding object relationships where they are not needed is considered a bad OO design practice for many reasons, and should be avoided (see details in the comments below).

More importantly, keeping table references inside cells adds code complexity and can lead to errors, because UITableViewCells are reusable. It is no coincidence that UIKit does not include a cell.parentTable property. If you define your own you must add code to manage it, and if you fail to do so effectively you can introduce memory leaks (i.e., cells live past the lifetime of their table).

Because typically you'll be using the category above when a user interacts with a cell (execute for a single cell), and not when laying-out the table in [tableView:cellForRowAtIndexPath:] (execute for all visible cells), the runtime cost should be insignificant.

Solution 3 - Iphone

Xcode 7 beta, Swift 2.0

This works fine for me, in my opinion it has nothing to do with the hierarchy or whatever. I had no trouble with this approach so far. I've used this for many async callbacks (ex. when an API request is done).

TableViewCell class

class ItemCell: UITableViewCell {
    
    var updateCallback : ((updateList: Bool)-> Void)? //add this extra var
    
    @IBAction func btnDelete_Click(sender: AnyObject) {
        let localStorage = LocalStorage()
        if let description = lblItemDescription.text
        {
            //I delete it here, but could be done at other class as well.
            localStorage.DeleteItem(description) 
        }
        updateCallback?(updateList : true)
        
    }
}

Inside table view class that implements the DataSource and Delegate

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell: ItemCell = self.ItemTableView.dequeueReusableCellWithIdentifier("ItemCell") as! ItemCell!
    cell.updateCallback = UpdateCallback //add this extra line
    cell.lblItemDescription?.text = self.SomeList[indexPath.row].Description
    return cell
}

func UpdateCallback(updateTable : Bool) //add this extra method
{
    licensePlatesList = localStorage.LoadNotificationPlates()
    LicenseTableView.reloadData()
}

Ofcourse you can put any variable in the updateCallback and change it's function in the tableView accordingly.

Someone might want to tell me if it is save to use though, just to be sure.

Solution 4 - Iphone

You have to add a reference back to the UITableView when you construct the table view cell.

However, almost certainly what you really want is a reference to your UITableViewController... that requires the same thing, set it as a delegate of the cell when you build the cell and hand it to the table view.

An alternate approach if you are wiring up actions is to build the cells in IB, with the table view controller as the files owner - then wire up buttons in the cell to actions in the table view controller. When you load the cell xib with loadNibNamed, pass in the view controller as the owner and the button actions will be wired back to the table view controller.

Solution 5 - Iphone

If you have custom classes for your UITableViewCells, you can add an id type variable in your cell's header, and synthesize the variable. After you set the variable when you load the cell, you are free to do what you please with the tableview or any other higher view without much hassle or overhead.

cell.h

 // interface
 id root;
 
 // propery 
 @property (nonatomic, retain) id root;

cell.m

> @synthesize root;

tableviewcontroller.m

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
  // blah blah, traditional cell declaration
  // but before return cell;
  cell.root = tableView;
}

Now you can call any of the tableview's methods from within your cell using the root variable. (e.g., [root reloadData]);

Ah, takes me back to the good old days of flash programming.

Solution 6 - Iphone

The two methods in other answers are: (A) store a reference to the table, or (B) walk up the superviews.

I'd always use something like (A) for model objects and (B) for table cells.

Cells

If you are dealing with a UITableViewCell, then AFAIK you must either have the UITableView at hand (say you are in a table delegate method), or are dealing with a visible cell that is in the view hierarchy. Otherwise, you may well be doing something wrong (please note the "may well").

Cells are liberally reused and if you happen to have one that is not visible then the only real reason that cell exists is because of iOS UITableView performance optimization (a slower iOS version would have released and hopefully dealloc'd the cell when it moved off screen) or because you have a specific reference to it. I guess this is probably the reason that table cells are not endowed with a tableView instance method.

So (B) gives the right result for all iOS's so far, and all future ones until they radically change how views work.

Though in order to avoid writing generalizable code over and over, I'd use this:

+ (id)enclosingViewOfView:(UIView *)view withClass:(Class)returnKindOfClass {
  while (view&&![view isKindOfClass:returnKindOfClass]) view=view.superview;
  return(view);
}

and a convenience method:

+ (UITableView *)tableForCell:(UITableViewCell *)cell {
  return([self enclosingViewOfView:cell.superview withClass:UITableView.class]);
}

(or categories if you like)

BTW, if you are concerned about the effect of a loop with 20 or so iterations of that size on your app performance,.. don't.

Models

If you are talking about the model object that is displayed in the cell, then definitely that model could/should know about its parent model, which may be used to find, or trigger changes in, the table(s) that the cell's model might be displayed in. This is like (A), but less brittle with future iOS updates (eg one day they might make the UITableViewCell reuse cache exist per reuseidentifier, rather than per reuseidentifier per tableview, on that day all the implementations that use the weak reference method will break).

Th model method would be used for changes to the data displayed in the cell (i.e. model changes) since changes will propagate wherever the model is displayed (eg. some other UIViewController somewhere else in the app, logging, ...)

The cell method would be used for tableview actions, which would likely always be a bad idea if the cell isn't even a subview of a table (though it's your code, go nuts).

Either way, use a unit test rather than assuming that seemingly cleaner code just works when they update iOS.

Solution 7 - Iphone

UITableView *tv = (UITableView *) self.superview.superview;
UITableViewController *vc = (UITableViewController *) tv.dataSource;

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMichael GrinichView Question on Stackoverflow
Solution 1 - IphonejbrennanView Answer on Stackoverflow
Solution 2 - IphoneDTsView Answer on Stackoverflow
Solution 3 - IphoneCularBytesView Answer on Stackoverflow
Solution 4 - IphoneKendall Helmstetter GelnerView Answer on Stackoverflow
Solution 5 - IphoneSold Out ActivistView Answer on Stackoverflow
Solution 6 - IphonewilsView Answer on Stackoverflow
Solution 7 - IphoneGankView Answer on Stackoverflow