iOS9,为什么在dequeueReusableCellWithIdentifier中调用自定义UITableViewCell的layoutSubviews:forIndexPath如果使用systemLayoutSizeFittingSize

我的问题的更具体的描述 :在iOS9中,为什么我的自定义UITableViewCell的layoutSubviewsdequeueReusableCellWithIdentifier:forIndexPath ,如果使用systemLayoutSizeFittingSize来计算heightForRowAtIndexPath的单元格高度?

因此, layoutSubviews在由cellForRowAtIndexPath:返回之前被调用,并且我不能再使用layoutSubviews来确定该单元是否在屏幕上,并且存在约束处理的几个问题。

对于复杂的自定义UITableViewCells,我更喜欢对计算和应用约束应运行的代码使用layoutSubview方法。 由于自定义UITableViewCell很复杂 ,我在tableView:heightForRowAtIndexPath使用systemLayoutSizeFittingSize来通过自动布局计算高度。 这在iOS8中完美运行,但在iOS9中打破了。

为了找出根本原因,我做了一个最小的项目,包括一个自定义的UITableViewCell,它有一个带有约束集的自定义标签:

 class testCell : UITableViewCell { @IBOutlet weak var labelTest: UILabel! required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) print("testCell - init:coder") } override func layoutSubviews() { super.layoutSubviews() print("testCell - layoutSubviews 💚💚💚") } func setLabelText(text : String) { self.labelTest?.text = text } } 

和tableView的dataSource:

 func numberOfSectionsInTableView(tableView: UITableView) -> Int { return 1 } func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 1 } func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { if let cell = tableView.dequeueReusableCellWithIdentifier("testCell") as? testCell { cell.setLabelText(String(indexPath.row)) print("🔸🔸🔸 tableview systemLayoutSizeFittingSize for \(indexPath.row)") let size = cell.systemLayoutSizeFittingSize(CGSizeMake(tableView.bounds.size.width, 1)) return size.height } return 0 } func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let char = indexPath.row > 0 ? "🔴" : "🔵" print(char+char+char+char+char+char+char+char+char+char+char+char+char+char+char+char+char+char) print("tableview start... dequeueReusableCellWithIdentifier for \(indexPath.row)") let cell = tableView.dequeueReusableCellWithIdentifier("testCell", forIndexPath: indexPath) print("tableview end... dequeueReusableCellWithIdentifier for \(indexPath.row)") print(char+char+char+char+char+char+char+char+char+char+char+char+char+char+char+char+char+char) if let cell = cell as? testCell { cell.setLabelText(String(indexPath.row)) } return cell } 

日志显示layoutSubviews在dequeueReusableCellWithIdentifier中调用:forIndexPath

testCell – init:编码器
🔸🔸🔸tableviewsystemLayoutSizeFittingSize为0
testCell – init:编码器
🔸🔸🔸tableviewsystemLayoutSizeFittingSize为0
testCell – init:编码器
🔸🔸🔸tableviewsystemLayoutSizeFittingSize为0
testCell – init:编码器
🔸🔸🔸tableviewsystemLayoutSizeFittingSize为0
🔵🔵🔵🔵🔵🔵🔵🔵🔵🔵🔵🔵🔵🔵🔵🔵🔵🔵
tableview start … dequeueReusableCellWithIdentifier为0
testCell – init:编码器
testCell – init:编码器
🔸🔸🔸tableviewsystemLayoutSizeFittingSize为0
testCell – layoutSubviews💚💚💚
tableview end … dequeueReusableCellWithIdentifier为0
🔵🔵🔵🔵🔵🔵🔵🔵🔵🔵🔵🔵🔵🔵🔵🔵🔵🔵
testCell – init:编码器
🔸🔸🔸tableviewsystemLayoutSizeFittingSize为0
testCell – layoutSubviews💚💚💚

让我们关注2的两行之间的日志:

tableview start … dequeueReusableCellWithIdentifier为0
testCell – init:编码器
testCell – init:编码器
🔸🔸🔸tableviewsystemLayoutSizeFittingSize为0
testCell – layoutSubviews💚💚💚
tableview end … dequeueReusableCellWithIdentifier为0

  • 分析:
    *第一个testCell – init:编码器dequeueReusableCellWithIdentifier:forIndexPath调用dequeueReusableCellWithIdentifier:forIndexPath ,由cellForRowAtIndexPath调用
    *第二个testCell – init:编码器dequeueReusableCellWithIdentifier调用,由heightForRowAtIndexPath调用
    *我在testCell上设置了一个断点– layoutSubviews ,并确认它是由dequeueReusableCellWithIdentifier:forIndexPath调用的dequeueReusableCellWithIdentifier:forIndexPath ,由cellForRowAtIndexPath调用

  • 得出结论:
    由于未知原因,自定义UITableViewCell的layoutSubviewsdequeueReusableCellWithIdentifier:forIndexPath cellForRowAtIndexPath: dequeueReusableCellWithIdentifier:forIndexPath调用cellForRowAtIndexPath:

寻找解决方案

  1. 第一个解决方案在于function: heightForRowAtIndexPath:
    如果我手动计算单元格的高度而不是使用systemLayoutSizeFittingSize ,它就可以了! dequeueReusableCellWithIdentifier:forIndexPath不会调用layoutSubviews
    但是这种方法并不方便,因为细胞高度的计算可能太复杂了。
    以下是修改后的代码和日志:

     func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { return 100 // might be a very complex calculation } 

    🔵🔵🔵🔵🔵🔵🔵🔵🔵🔵🔵🔵🔵🔵🔵🔵🔵🔵
    tableview start … dequeueReusableCellWithIdentifier为0
    testCell – init:编码器
    tableview end … dequeueReusableCellWithIdentifier为0
    🔵🔵🔵🔵🔵🔵🔵🔵🔵🔵🔵🔵🔵🔵🔵🔵🔵🔵
    testCell – layoutSubviews💚💚💚

  2. 第二个解决方案在于function: cellForRowAtIndexPath:
    我尝试使用dequeueReusableCellWithIdentifier而不是dequeueReusableCellWithIdentifier:forIndexPath ,下面的日志显示layoutSubviews没有在dequeueReusableCellWithIdentifier直接调用,但它仍然在tableView:_configureCellForDisplay调用tableView:_configureCellForDisplay (不确定时间)。 我不能确认这是一个解决方案。
    无论如何, dequeueReusableCellWithIdentifier:forIndexPath是除dequeueReusableCellWithIdentifier之外的推荐方法。 如果我愿意,我仍然更喜欢前一个。

    testCell – init:编码器
    🔸🔸🔸tableviewsystemLayoutSizeFittingSize为0
    testCell – init:编码器
    🔸🔸🔸tableviewsystemLayoutSizeFittingSize为0
    testCell – init:编码器
    🔸🔸🔸tableviewsystemLayoutSizeFittingSize为0
    testCell – init:编码器
    🔸🔸🔸tableviewsystemLayoutSizeFittingSize为0
    🔵🔵🔵🔵🔵🔵🔵🔵🔵🔵🔵🔵🔵🔵🔵🔵🔵🔵
    tableview start … dequeueReusableCellWithIdentifier为0
    testCell – init:编码器
    tableview end … dequeueReusableCellWithIdentifier为0
    🔵🔵🔵🔵🔵🔵🔵🔵🔵🔵🔵🔵🔵🔵🔵🔵🔵🔵
    testCell – init:编码器
    🔸🔸🔸tableviewsystemLayoutSizeFittingSize为0
    testCell – layoutSubviews💚💚💚
    testCell – layoutSubviews💚💚💚

问题:

虽然我找到了2个解决方案,但它们都不令人满意。
最好的方法是我仍然可以使用systemLayoutSizeFittingSize来计算单元格高度,并使用dequeueReusableCellWithIdentifier:forIndexPath来获取有效的单元格。
唯一的问题是我需要确认layoutSubviews在函数cellForRowAtIndexPath返回之前没有被调用(在当前情况下,它由dequeueReusableCellWithIdentifier:forIndexPath调用dequeueReusableCellWithIdentifier:forIndexPath在函数cellForRowAtIndexPath返回之前),这样我就可以使用这个方法对于将计算和应用单元格约束运行的代码,并且可以确认在到达layoutSubviews ,单元格已在屏幕上。
有没有人有任何好主意?
谢谢。