从UITableViewCell引用到父级UITableView?

73

有没有办法从UITableViewCell中访问所属的UITableView

7个回答

126

在单元格中存储对tableView的weak引用,这需要在表格的数据源方法-tableView:cellForRowAtIndexPath:中设置。

这比依赖于self.superview始终准确地是tableView要更加健壮。谁知道苹果将来会如何重新组织UITableView的视图层次结构。


12
为了安全起见,您可能需要添加一个“[self.superview isKindOfClass:[UITableView class]]”检查,以防将来视图层次结构发生变化。 - Frank Szczerba
3
实际上,这似乎有点像黑客方式,因为如果某些表格设计先有一些容器,并且它们包含单元格,那么cell.superview可能不是表格,因此它在这里对UITableView的实现做出了假设。 - nonopolarity
4
是的,他们在iOS 7中进行了更改 :) - Calin Chitu
2
您可以按照下面的“类别”建议,遍历子视图而不依赖于任何特定的视图层次结构。这保证了它将在未来版本中正常工作。这样,您就不必在代码中散布引用并增加对象耦合。 - DTs
2
@JamesDonald 苹果也没有添加“parentTableView”方法,因此很难理解苹果的动机。在许多情况下,不需要引用表格,因此添加引用会消耗字节。添加引用可能会导致问题,因为人们倾向于在单元格中引用表格。配置单元格时,单元格可以无意中调用表格。鉴于代码的简单性,我选择使用弱引用。最好使用一点内存,而不是不断地上升层次结构,从而消耗某人的电池。 - Gerard
显示剩余5条评论

18

以下是更好的方法,不依赖于特定的UITableView层次结构。只要UITableView类名不完全更改,它将适用于任何未来的iOS版本。虽然这种情况极不可能发生,但如果确实发生了,您无论如何都必须修改代码。

只需导入下面的类别,使用[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.


更新
有一些评论讨论保留弱引用是否是更好的方法。这取决于您的情况。遍历视图层次结构会带来一些小的运行时开销,因为您需要循环直到识别出目标UIView。您的视图有多深?另一方面,每个单元格保留一个引用只会有很小的内存开销(弱引用毕竟是指针),通常在不需要它们的地方添加对象关系被认为是一种不良的OO设计实践,应该避免(请参见下面的评论中的详细信息)。

更重要的是,将表格引用放在单元格内部会增加代码复杂性并可能导致错误,因为UITableViewCells是可重用的。UIKit中没有包含cell.parentTable属性,这不是巧合。如果您定义了自己的属性,必须添加代码来管理它,如果未能有效地执行此操作,可能会引入内存泄漏(即,单元格的生命周期超过其表格的生命周期)。

由于通常您将在用户与单元格交互时使用上述类别(仅对单个单元格执行),而不是在[tableView:cellForRowAtIndexPath:]中布置表格(对所有可见单元格执行),因此运行时成本应该是微不足道的。


3
如果你发现自己需要遍历子视图,你应该问问自己为什么需要这样做。iOS 7变更UITableViewCell时的一个坏主意,可以最好地说明这一点,因为这将使其始终返回nil。如果您需要从单元格中获取表视图,弱引用是正确的方法。 - Cameron Lowell Palmer
4
不,这在 iOS7 中仍然可以正常使用,并且它将在未来的 iOS 版本中继续运行。我们应用商店中的许多应用程序仍在使用此代码而没有任何问题,只需按照代码注释中建议的放置在正确的位置即可。到处存储引用并不会促进清洁设计,许多设计书籍建议避免这样做。 - DTs
3
良好的OO设计,特别是迪米特法则(Law of Demeter),指出“单元应该只对其他单元有限的了解,并且只在绝对必要的情况下这样做”。这样可以促进松散的单元耦合和可重用性。保持对子视图的引用实际上是违反了LoD,而不是以一种与视图层次结构无关的方式遍历视图。后者促进了松散耦合和良好的OO可重用设计,事实上也很好地尊重了LoD。也许您不喜欢依赖于固定的硬编码层次结构来遍历子视图,但这里并非如此。 - DTs
3
UITableViewCellUIKit 的设计约束,必须位于 UITableView 下方。以 UIView hierarchy-agnostic 的方式查找 UITableView 实例不会引入额外的关系。在软件工程中,这时称 UITableViewCell 为松散耦合(loosely coupled)。相比之下,将特定的 UITableView 实例的引用保持在 UITableViewCell 内被认为是紧密耦合的,因为会引入直接关系。在UIKit中,没有提供 UITableViewCell.parentTable 属性是有原因的。 - DTs
3
如果你认为这里讨论的两种解决方案“在实质上是相同的”,我建议你学习一下苹果公司的“Objective-C编程指南”,这是一本非常优秀的入门指南。它应该能够帮助你澄清这里讨论的两个完全对立的解决方案之间的区别。顺便说一句,考虑到原始问题只能通过这里讨论的两种竞争性方法中的其中一种来解决,你最后的评论似乎暗示着OP不应该解决这个普遍且简单的问题,而是应该放弃。这没有任何意义。 - DTs
显示剩余8条评论

15

Xcode 7 beta, Swift 2.0

在我看来,这对我来说很好用,与层次结构或其他什么无关。到目前为止,我在许多异步回调中都使用了这种方法(例如当API请求完成时)。

TableViewCell类

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)

    }
}

实现DataSource和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()
}

当然,您可以将任何变量放入updateCallback中,并相应地更改tableView的函数。

不过,有人可能想告诉我是否安全使用,以确保安全。


我已经为两个不同的单元格实现了这段代码。在第一个单元格中,我有updateCallback,在第二个单元格中,我有updateCallback2。我知道100%我的所有方法和变量都被正确命名,然而,updateCallback2是nil(updateCallback没问题)。可能是什么原因导致它为空? - Schuey999
你设置了吗?cell.updateCallback2 = UpdateCallback2 - CularBytes
谢谢您的回答。如果我可以补充一下,您可以使用可选链来避免检查updateCallback是否为nil,就像这样:updateCallback?(updateList : true)。如果回调被设置了,调用就会发生。 - vguerra

7

在构建表视图单元格时,您必须添加对UITableView的引用。

然而,您真正想要的几乎肯定是对UITableViewController的引用...这需要相同的操作,在构建单元格时将其设置为单元格的代理并将其交给表视图。

如果您正在连接操作,则可以使用另一种方法,在IB中构建单元格,并将表视图控制器设置为文件所有者-然后将单元格中的按钮连接到表视图控制器中的操作。当您使用loadNibNamed加载单元格xib时,将视图控制器作为所有者传递,按钮操作将被连接回表视图控制器。


4

如果您的UITableViewCells有自定义类,您可以在cell的头文件中添加一个id类型的变量,并将变量合成。当您加载单元格时设置变量后,您可以自由地对tableview或任何其他更高级别的视图进行操作,而不需要太多麻烦或开销。

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

现在你可以使用root变量从单元格内调用任何tableview的方法(例如,[root reloadData])。
啊,这让我回想起Flash编程的好日子。

6
尽管这样做可以让表格视图拥有单元格,但你实际上并不希望它被保留。这个更好: @property (nonatomic, assign) id root; - João Josézinho

2
其他答案中的两种方法是:(A)存储表格的引用,或者(B)向上遍历superview。
对于模型对象,我总是使用类似(A)的方法,对于表格单元格,我会使用(B)。
单元格
如果你正在处理UITableViewCell,那么据我所知,你必须要有UITableView(比如说你在表格代理方法里),或者正在处理一个在视图层次结构中的可见单元格。否则,你可能做错了什么(请注意“可能”)。
单元格会被大量重用,如果你恰好有一个不可见的单元格,那么这个单元格存在的唯一原因是iOS UITableView的性能优化(较慢的iOS版本会在它移出屏幕后释放和dealloc掉它),或者是因为你有一个特定的引用。我想这可能就是表格单元格没有tableView实例方法的原因。
因此,(B)可以在到目前为止的所有iOS版本中得到正确的结果,直到他们彻底改变视图工作的方式。
尽管为了避免一遍又一遍地编写通用代码,我会使用这个:
+ (id)enclosingViewOfView:(UIView *)view withClass:(Class)returnKindOfClass {
  while (view&&![view isKindOfClass:returnKindOfClass]) view=view.superview;
  return(view);
}

以及一个便利的方法:

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

(或者分类,如果您喜欢)

顺便说一下,如果您担心循环大约有20次迭代对应用程序性能的影响,请不要担心。

模型

如果您谈论的是在单元格中显示的模型对象,那么该模型肯定可以/应该知道其父模型,这可能用于查找或触发更改表格(s)中的单元格模型可能会显示。 这就像(A),但随着未来iOS更新的变化而变得不太脆弱(例如,有一天他们可能使UITableViewCell重用缓存存在于重用标识符上,而不是每个tableview的重用标识符,到那一天,所有使用弱引用方法的实现都将失败)。

model方法将用于更改单元格中显示的数据(即模型更改),因为更改将在模型显示的任何地方传播(例如,应用程序中的其他UIViewController,记录日志等)

cell方法将用于tableview操作,如果单元格甚至不是表的子视图,则很可能总是一个坏主意(虽然这是您的代码,请随意)。

无论哪种方式,都请使用单元测试,而不是假设看似更干净的代码在更新iOS时就可以正常工作。


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

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