从UITableViewCell到父UITableView的引用?


Answers:


120

weak-tableView:cellForRowAtIndexPath:表的dataSource中设置的单元格中存储对tableView的引用。

这比依赖self.superview始终始终保持tableView易碎要好。谁知道苹果UITableView将来会如何重组视图层次结构。


12
为了安全起见,您可能需要添加“ [self.superview isKindOfClass:[UITableView类]]”检查,以防万一视图层次结构将来发生变化。
Frank Szczerba 09年

3
实际上,这似乎有些骇人听闻,因为如果某些表设计首先有一些容器,并且它们包含单元格(在这种情况下,cell.superview可能不是表)该
怎么办

4
是的,他们确实使用IOS 7进行了更改:)
Calin Chitu

2
您可以遍历子视图,而无需依赖任何特定的视图层次结构,Category如下所示。这保证了它将在将来的版本中工作。这样,您就不必在整个代码中分散引用并增加对象耦合。
DT 2014年

2
@JamesDonald苹果也未添加“ parentTableView”方法,因此不清楚是哪种原因促使了苹果。在很多情况下,不需要对该表进行引用,因此添加引用会减少字节数。添加引用可能会引起问题,因为人们倾向于引用单元格中的表。重用单元格后,配置单元格可能会无意间调用表。鉴于代码的简单性,我的参考文献很少。最好花一点内存,而不是消耗不断增加的电量。
杰拉德2014年

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
@mattcurtis如果您发现自己正在行走子视图,则应问自己为什么?iOS 7对UITableViewCell的更改将使此错误始终返回nil,从而很好地说明了这个错误的主意。如果您需要从单元格中获取表格视图,则要使用弱引用。
卡梅隆·洛厄尔·帕尔默

4
@CameronLowellPalmer不,这在iOS7中仍然可以正常使用,并且将在以后的iOS版本中继续使用。我们在应用商店中的许多应用仍然可以毫无问题地使用此代码,只需按照代码注释中的建议将其放在正确的位置即可。在各处存储参考不会促进干净的设计,许多设计书籍都建议不要这样做。
DT 2014年

3
@CameronLowellPalmer如果您希望读者考虑您的意见,则有必要对其进行辩护。在没有说明问题的情况下将想法陈述为教条,不会帮助任何人对其进行评估或从中学习。上面的代码是通用的,不会刹车,并且我提供了注释,以显示弱引用方法的优缺点。如果您还有其他贡献,请详细说明。到目前为止,我们已经看到您还没有完全理解上面的代码是如何工作的,因为您错误地声称它在iOS7中不起作用。
DT 2014年

3
@CameronLowellPalmer的良好OO设计,尤其是Demeter定律,指出“单位对其他单位的了解应该有限,只有在绝对必要时才这样做”。这是为了促进松散的单元耦合和可重用性。保留对子视图的引用实际上是对LoD的违反,而不是以视图层次结构不可知的方式遍历视图。后者促进了松散的耦合和良好的OO可重用设计,并且实际上很好地尊重了LoD。也许您不喜欢的是在依赖固定的硬编码层次结构的同时遍历子视图,但事实并非如此。
DT 2014年

3
@CameronLowellPalmer aUITableViewCellUIKit居住在a下方的设计的限制UITableView。寻找一个的该实例UITableViewUIView hierarchy-agnostic方式介绍没有额外的关系。在UITableViewCell随后被说成是loosely coupled在软件工程。相反,由于引入了直接关系,因此在内维护对特定 UITableView实例的引用UITableViewCell被视为紧密耦合。UITableViewCell.parentTableUIKit中没有提供属性并非偶然。
DT 2014年

14

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 2015年

感谢您的回答。如果我可能会增加,你可以抽出检查updateCallback是通过使用可选的链接这样的零:updateCallback?(updateList : true)。如果设置了回调,则将发生呼叫。
vguerra

7

构造表视图单元时,必须将引用添加回UITableView。

但是,几乎可以肯定,您真正想要的是对UITableViewController的引用...它需要做同样的事情,在构建单元格并将其移交给表格视图时将其设置为单元格的委托。

如果要连接动作,另一种方法是使用表视图控制器作为文件所有者,在IB中构建单元格-然后将单元格中的按钮连接到表视图控制器中的动作。当使用loadNibNamed加载单元xib时,以所有者的身份传入视图控制器,并且按钮操作将重新连接回表视图控制器。


4

如果您有UITableViewCells的自定义类,则可以在单元格的头中添加一个id类型变量,然后合成该变量。加载单元格后设置变量后,您可以自由地使用tableview或任何其他更高的视图执行所需的操作,而不会造成太多麻烦或开销。

单元格

 // interface
 id root;

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

细胞

@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
您实际上并不希望保留它。tableview通常拥有该单元格。这将是更好的 @property (nonatomic, assign) id root;
若昂Josézinho

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

1

其他答案中的两种方法是:(A)存储对表的引用,或(B)沿父视图。

我将始终对模型对象使用(A),对于表单元格使用(B)。

细胞

如果要处理UITableViewCell,则AFAIK必须要么有UITableView(例如您在表委托方法中),要么要处理视图层次结构中的可见单元格。否则,您可能做错了什么(请注意“可能很好”)。

单元格被大量重用,如果碰巧有一个不可见的单元格,则该单元格存在的唯一真正原因是由于iOS UITableView性能优化(较慢的iOS版本会释放并希望在移出屏幕时重新分配该单元格) ),或者因为您对此有特定的引用。我猜这可能是表单元格没有使用tableView实例方法的原因。

因此,(B)可以为迄今为止的所有iOS以及所有将来的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次左右的循环迭代对应用程序性能的影响,请不要这样做。

楷模

如果您要讨论的是显示在单元格中的模型对象,那么肯定可以/应该知道该模型的父模型,该父模型可以用于查找或触发该单元格模型可能在其中的表就像(A)一样,但是在将来的iOS更新中不那么脆弱(例如,有一天它们可能使UITableViewCell重用缓存针对每个复用标识符(而不是针对每个tableview的每个复用标识符)存在,那天所有使用弱函数的实现引用方法会中断)。

模型将用于更改数据的方法在小区(即模型的改变)显示,因为无论是显示模式的改变将传播(如一些其他的UIViewController别的地方的应用程序,记录,...)

细胞的方法将被用于实现代码如下行动,这将可能永远是一个坏主意,如果该小区不是表甚至一个子视图(虽然这是你的代码,发疯)。

无论哪种方式,都应使用单元测试,而不是假设看似干净的代码在更新iOS时才起作用。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.