如何从UITableViewCell获取UITableView?

105

我有一个与对象相关联的UITableViewCell,我需要知道这个单元格是否可见。从我所做的研究来看,这意味着我需要以某种方式访问包含它的UITableView(从那里,有几种方法可以检查它是否可见)。因此,我想知道UITableViewCell是否有指向UITableView的指针,或者是否有其他方法从单元格中获取指针?


2
这是什么目的? - max_
[cell superView] 可能是什么? - Chris Loonam
6
值得解释一下为什么你认为需要这个功能,因为这可能是糟糕设计的迹象,我真的想不出有很多合法理由让单元格知道它是否在屏幕上。 - Paul.s
@PJ_Finnegan 那是在我以前的工作中的两年半之前。对不起,我不记得我们当时在做什么或为什么要这样做。 - chadbag
1
@chadbag 不用担心,希望我能给有同样问题的其他人提供一些想法。 - PJ_Finnegan
显示剩余2条评论
21个回答

159

为避免检查iOS版本,需要从单元格视图向上迭代遍历其父视图,直到找到UITableView:

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()
}

1
谢谢。看起来在iOS 8中又有所改变,这样可以很好地处理所有版本。 - djskinner
2
我建议在单元格中添加一个对表视图的弱引用,以避免未来更新中的兼容性问题。 - Cenny
1
这是一种比较脆弱的方式。如果视图层次结构在未来发生变化,它将无法正常工作。 - RomanN
2
最好创建一个弱引用属性,实际上保存指向tableview的指针。在子类中使用 @property (weak, nonatomic) UITableView *tableView;,并在 tableView:cellForRowAtIndexPath: 中设置 cell.tableView = tableView; - Alejandro Iván
目前的经验是,在出列一个单元格后,表视图不会立即作为单元格的父视图出现 - 如果我将单元格滚动出视图并再次滚回来,我会发现表视图作为父视图(递归搜索如其他答案所述)。自动布局和可能的其他因素很可能是原因。 - Jonny

48

Swift 5扩展

递归地

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

使用循环

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
    }
}

一条经验法则是不要使用强制解包。 - thibaut noah
2
@thibautnoah 在解包之前有一个 view != nil 检查。尽管如此,代码仍然进行了优化。 - Orkhan Alikhanov

48
在iOS7 beta 5中,UITableViewWrapperViewUITableViewCell的父视图。此外,UITableView也是UITableViewWrapperView的父视图。

因此,在iOS 7中的解决方案如下:

UITableView *tableView = (UITableView *)cell.superview.superview;

而在iOS 6及以下版本中的解决方案为:

UITableView *tableView = (UITableView *)cell.superview;


5
哦,这是一个令人难以应对的 API 变更。如果要同时支持 iPhone 6 和 7,苹果公司有什么最佳分支实践? - Ryan Romanchuk
@RyanRomanchuk 这里有一个好的建议:https://devforums.apple.com/message/865550#865550 -- 在创建单元格时,创建一个弱指针指向相关的tableView。或者,创建一个UITableViewCell类别,其中包含一个新方法relatedTableView,该方法检查iOS版本并返回适当的superView。 - memmons
3
请为UIView编写一个递归类。您无需检查版本,只需调用superview,直到找到表格视图单元格或视图堆栈的顶部。这是我在iOS 6中使用的方法,并且在iOS 7中没有修改就可以使用。而且应该仍然可以在iOS 8中使用。 - Steven Fisher
不好意思,我的意思是直到你找到表视图。 :) - Steven Fisher

25

iOS7之前,UITableViewCell的父视图是包含它的UITableView。从iOS7 GM版本开始(应该也会在公共版本中),UITableViewCell的父视图是一个名为UITableViewWrapperView的视图,其父视图是UITableView。有两种解决方案。

解决方案1:创建UITableViewCell类别

@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

这是一个很好的替代方案,可以取代使用cell.superview,让重构现有代码变得简单--只需查找并替换为[cell relatedTable],并添加一个断言以确保如果视图层次结构在未来发生更改或还原,则会立即在测试中显示。

解决方案#2:向UITableViewCell添加一个弱UITableView引用

@interface SOUITableViewCell

   @property (weak, nonatomic) UITableView *tableView;

@end

这是一个更好的设计,但需要在现有项目中进行一些代码重构。在您的tableView:cellForRowAtIndexPath中使用SOUITableViewCell作为您的单元格类或确保您的自定义单元格类是从SOUITableViewCell子类化并将tableView分配给单元格的tableView属性。在单元格内,您可以使用self.tableView来引用包含的tableview。


我不同意解决方案2是更好的设计。它有更多的故障点,并需要纪律性的手动干预。第一个解决方案实际上相当可靠,尽管我会按照@idris在他的答案中建议的方式实施它,以便更好地保护未来。 - devios1
1
测试[self.superview.superview isKindOfClass:[UITableView class]]应该放在第一位,因为iOS 7越来越流行。 - CopperCash

7

Swift 2.2解决方案。

这是一个针对UIView的扩展,它可以递归搜索特定类型的视图。

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
    }
}

更加紧凑(感谢kabiroberai):
import UIKit

extension UIView {
    func lookForSuperviewOfType<T: UIView>(type: T.Type) -> T? {
        return superview as? T ?? superview?.superviewOfType(type)
    }
}

在您的单元格中,只需调用它:
let tableView = self.lookForSuperviewOfType(UITableView)
// Here we go

请注意,UITableViewCell 仅在执行 cellForRowAtIndexPath 后才会添加到 UITableView 上。

你可以将整个 lookForSuperviewOfType: 方法体压缩成一行,使其更加 Swift 风格:return superview as? T ?? superview?.superviewOfType(type) - kabiroberai
@kabiroberai,谢谢你。我已经将你的建议添加到答案中了。 - Mehdzor
递归调用使用的名称需要匹配。因此,这应该写成:... ?? superview?.lookForSuperviewOfType(type) - robert

7
如果它是可见的,那么它有一个父视图。而且...惊喜...父视图是一个UITableView对象。
然而,有一个父视图并不保证在屏幕上显示。但是UITableView提供了方法来确定哪些单元格是可见的。
不,没有从单元格到表格的专用引用。但是,当你子类化UITableViewCell时,你可以引入一个并在创建时设置它。(在我考虑子视图层次结构之前,我经常这样做。)
iOS7更新: 苹果在这里改变了子视图层次结构。通常情况下,当处理未详细记录的事物时,存在着事物会发生变化的风险。最好的方法是“向上爬”视图层次结构,直到找到一个UITableView对象。

14
在iOS7中这已经不再是真实的情况了,在iOS7 beta5版本中,UITableViewWrapperView成为UITableViewCell的父视图... 这正是我现在遇到的问题。 - Gabe
谢谢您的评论。我得检查一下我的代码。 - Hermann Klecker

7
无论您通过调用super view还是通过响应者链来完成什么操作,都会非常脆弱。如果cell想要了解某些信息,最好的方法是将一个对象传递给cell,该对象响应于回答cell想要询问的问题的某个方法,并让控制器实现确定要回答什么的逻辑(从您的问题中我猜测cell想要知道某些东西是否可见)。
在cell中创建委托协议,将cell的委托设置为tableViewController,并将所有ui“控制”逻辑移动到tableViewCotroller中。
表格视图单元格应该是dum视图,只显示信息。

也许通过委托甚至更好。由于父级给我带来了问题,我暂时使用传递的表引用。 - Cristi Băluță
1
我所描述的基本上是使用委托模式 :) - Javier Soto
这应该是被接受的答案,人们看到这个问题,看到150多个赞同的答案,他们认为从单元格获取tableView并且甚至更多地保持对它的强引用是可以的,不幸的是。 - Alex Terente

6
我创建了一个UITableViewCell的类别来获取它的父级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

您好,


3

以下是基于以上答案的Swift版本,我已经将其泛化为ExtendedCell以供后续使用。

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
    }
}

希望这能帮到你 :)

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接