How to get UITableView from UITableViewCell?
Asked Answered
R

21

105

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?

Ruttish answered 29/3, 2013 at 21:26 Comment(7)
What's the purpose of this?Verse
[cell superView] maybe?Yorick
It's worth explaining why you think you need this - as this may be a sign of bad design as I cannot really think of many legitimate reasons for a cell to know if it is on screen or not.Muenster
@Muenster We have a gesture recognizer on an image in a cell and when the cell is touched, it opens up another overlay view, think popover style, that should overlay as many cells as needed to display properly. For this to work it needs the TableView or other view given to it to display in. Not really happy with the solutions but to get the effect desired getting the UITableView of the UITableViewCell is the best we have come up with.Fabe
@Fabe Can't you capture the reference to the UITableView in the IBAction/closure you use as the image touch event handler? Usually this is done in a UITableViewController which should be aware of the table view instance.Dmitri
@Dmitri That was 2 1/2 years ago at a former job. I don't remember what we were doing or why. Sorry.Fabe
@Fabe no worries, hopefully I gave an idea to someone else with the same problem.Dmitri
M
159

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()
}
Muffler answered 13/9, 2013 at 6:59 Comment(5)
Thanks. It would appear that this has changed once again in iOS 8 and this takes care of all versions nicely.Aguilera
I would recommend adding a weak reference to the tableview to the cell to avoid compatibility issues in future updates.Collator
Rather a weak way. It won't work if view's hierarchy changes in future.Aragats
It would be better to simply create a weak property that actually holds a pointer to the tableview. In the subclass @property (weak, nonatomic) UITableView *tableView; and in tableView:cellForRowAtIndexPath: just set cell.tableView = tableView;.Electroshock
Current experience is that after dequeuing a cell, a tableview is not appearing as a superview of the cell right away - if I scroll the cell out of view and back in again I do find a table view as a superview (recursive search as described in other answers). Autolayout and possibly other factors are likely reasons.Dukedom
S
48

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;

Sheetfed answered 23/8, 2013 at 16:53 Comment(4)
Oh man, this is a brutal API change. What's apple's best practice for branching if you support both 6 and 7?Raman
@RyanRomanchuk Here is one good suggestion: devforums.apple.com/message/865550#865550 -- create a weak pointer to your related tableView when the cell is created. Alternately, create a UITableViewCell category with a new method, relatedTableView which does a check for an iOS version and returns the appropriate superView.Iridescence
Write a recursive category on UIView for this. You don't need to check the version; just call superview until you find a table view cell or the top of the view stack. This is what I used in iOS 6, and it worked without modification in iOS 7. And should work sitll in iOS 8.Restore
Beg pardon; I meant until you find the table view. :)Restore
E
48

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
    }
}
Endive answered 29/1, 2017 at 11:24 Comment(2)
A rule of thumb is to not use force unwrappingChoirboy
@thibautnoah There is a view != nil check before unwrapping. Updated to cleaner code thoughEndive
I
25

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.

Iridescence answered 15/9, 2013 at 14:33 Comment(2)
I disagree that solution 2 is a better design. It has more points of failure and requires disciplined manual intervention. The first solution is actually quite reliable, though I would implement it as @idris suggested in his answer for a bit more futureproofing.Anaphylaxis
Test [self.superview.superview isKindOfClass:[UITableView class]] should be the first, 'cause the iOS 7 is more and more.Accessible
S
7

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.

Swoop answered 29/3, 2013 at 21:32 Comment(2)
This is no longer true in iOS7, In iOS7 beta5 UITableViewWrapperView is the superview of a UITableViewCell... causing me issues right nowParity
Thanks for the comment. I'll have to check some of my code then.Swoop
P
7

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.

Polynuclear answered 29/3, 2013 at 22:43 Comment(3)
Maybe even better through delegates. i'm temporarily using a passed reference to the table since the parent gave me problems.Jailbreak
What I described is basically using the delegate pattern :)Polynuclear
This should be the accepted answer, people see this question, see the 150+ upvote answer and they think is fine to get the tableView from the cell and even more maybe keep a strong reference to it unfortunately.Husbandman
M
7

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.

Melanymelaphyre answered 10/8, 2016 at 14:58 Comment(3)
You can compact the entire lookForSuperviewOfType: method body into one line, making it even swift-ey: return superview as? T ?? superview?.superviewOfType(type)Officer
@kabiroberai, thank you. I added your advice to the answer.Melanymelaphyre
The name used by the recursive call needs to match. So this needs to read: ... ?? superview?.lookForSuperviewOfType(type)Eaten
K
6

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,

Kreiner answered 28/9, 2013 at 0:47 Comment(0)
S
3

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 :)

Shapely answered 17/4, 2016 at 8:30 Comment(0)
T
2

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];
}
Terrilyn answered 22/8, 2013 at 3:42 Comment(0)
D
2

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.

Dorita answered 29/7, 2014 at 6:58 Comment(0)
T
2

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;
}
Turin answered 18/8, 2014 at 20:17 Comment(1)
This will loop forever if the cell is not yet in the table view when called. To fix, add a check for nil: for (view = self.superview; view && ![view isKindOfClass:UITableView.class]; view = view.superview);Signory
E
2
UITableView *tv = (UITableView *) self.superview.superview;
BuyListController *vc = (BuyListController *) tv.dataSource;
Eda answered 1/7, 2015 at 8:27 Comment(0)
M
1

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.

Marilla answered 9/12, 2013 at 18:52 Comment(0)
S
1

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
    }
}
Strachey answered 9/11, 2016 at 20:49 Comment(0)
E
0

this code `UITableView *tblView=[cell superview]; will give you an instance of the UItableview which contains the tabe view cell

Explant answered 2/4, 2013 at 7:49 Comment(1)
This will not work, as the immediate superview of a UITableViewCell is not a UITableView. As of iOS 7, the UITableViewCell superview is a UITableViewWrapperView. See the other answers in here for less fragile, more reliable approaches.Gnathonic
B
0

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.

Brilliantine answered 1/9, 2013 at 17:26 Comment(0)
H
0
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]: https://i.stack.imgur.com/C2uJa.gif

http://www.curiousfind.com/blog/646 Thank You

Halima answered 28/5, 2014 at 8:27 Comment(0)
G
0

You can get it with one line of code.

UITableView *tableView = (UITableView *)[[cell superview] superview];
Geometry answered 13/6, 2016 at 7:36 Comment(0)
I
0
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
    }
}
Indoors answered 19/8, 2016 at 20:5 Comment(0)
G
0

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
}
Grainger answered 4/1, 2018 at 9:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.