NSTableView reloadData(forRowIndexes:columnIndexes:) 破坏了自动布局

4
我有一个NSTableView,可以根据行的数据值交换不同的单元格视图。当模型更改时,我重新加载表格,表格的委托将为新数据提供正确的表格单元格视图。
该表格使用自动布局来处理其单元格视图。所有单元格视图最初都会正常加载。在模型更改后更新表格时,根据调用reloadData()reloadData(forRowIndexes:columnIndexes)而产生不同的结果。使用reloadData()时,单元格视图被加载且自动布局正常工作。如果使用reloadData(forRowIndexes:columnIndexes),则自动布局会产生完全不同和意外的结果。
我创建了一个示例项目来演示问题。 这里是项目设置的图像,包括对表格单元格视图设置的约束。有两个行模板,一个带有蓝色视图(偶数行),一个带有绿色视图(奇数行),应跨越表格宽度(减去一些填充)。控制器提供单元格视图:
class TableController: NSObject {
    @IBOutlet weak var tableView: NSTableView!
    var colorData = [1, 0, 1, 0]

    @IBAction func swapLine(_ sender: Any) {
        colorData[1] = (colorData[1] + 1) % 2
    //        tableView.reloadData()
        tableView.reloadData(forRowIndexes: [1], columnIndexes: [0])
    }
}

extension TableController: NSTableViewDataSource {
    func numberOfRows(in tableView: NSTableView) -> Int {
        return colorData.count
    }
}

extension TableController: NSTableViewDelegate {
    func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
        let cellId = (colorData[row]) % 2 == 0 ? "EvenCell" : "OddCell"
        return tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(cellId), owner: self)
    }
}

界面中的一个按钮只是交换了第一行的数据并重新加载数据。初始视图看起来像这样(交替显示绿色和蓝色方块)。如果使用reloadData(),它看起来像这样(第1行从蓝色变为绿色)。但是,如果使用reloadData(withRowIndexes:columnIndexes:),单元格视图会缩小到40个点,而不是其他情况下的480个点。这里是视图调试器中显示的具有错误大小的单元格视图以及显示模棱两可的宽度约束的截图(使用reloadData()时不会发生这种情况)。
文档提到,使用reloadData(forRowIndexes:columnIndexes:)重用了行视图,但使用reloadData()没有,我已经验证过了。我想这个行视图的重用是导致自动布局问题的原因,但我找不到关联。在SO、AppKit发布说明、WWDC视频、Google搜索或敲打桌子时什么都没有找到。如果能得到帮助,将不胜感激。
更新:这是ColorView的代码:
class ColorView: NSView {
    @IBInspectable var intrinsicHeight: CGFloat = 20
    @IBInspectable var color: NSColor = NSColor.blue

    override var intrinsicContentSize: NSSize {
        return NSSize(width: NSView.noIntrinsicMetric, height: intrinsicHeight)
    }

    override func draw(_ dirtyRect: NSRect) {
        color.setFill()
        dirtyRect.fill()
    }
}

自定义视图和颜色视图的类别是什么?你是否已经用NSView替换了NSTableCellView - Willeke
尝试使用(NSTableCellView的子类)单元格视图或具有与默认单元格视图相同的autoresizingMask的单元格视图。 - Willeke
谢谢@Willeke。我试了一下,但没有变化。 - smr
我无法重现您的问题。这是我的代码:https://github.com/nyousefi/Tables - Nima Yousefi
1
你仍然可以使用自动布局,但是不要使用intrinsicContentSize,而是使用NSTableViewDelegate方法func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat。您可以根据tableView的边界计算宽度,然后使用该宽度计算文本的行高。在textView上使用自动布局,以便它随着行高缩放。这是我在我们的应用程序中采用的方法,效果非常好。高度计算是最困难的部分,但无论采用哪种方法都必须处理它。 - Nima Yousefi
显示剩余6条评论
2个回答

1

我觉得我已经解决了问题。如果在返回单元格之前调用layoutSubtreeIfNeeded()(这样它的所有子视图,如动态文本,都已设置好),那么似乎可以正常工作。

func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
  //...

  cell.layoutSubtreeIfNeeded()
  return cell
}

我希望这有所帮助。


嗯...进一步的测试显示这确实有所帮助,但并不总是有效。真糟糕。 - Clifton Labrum
2
就此问题,苹果已经承认这是一个漏洞。等他们关闭雷达后,我会尝试更新。 - smr
1
当我这样做时,我失去了NSTextFields的宽度约束(它们只是停用,回退到intrinsicSize)。不得已使用removeRows,insertRows...无论是addSubview还是layoutSubtree的解决方法都似乎对我没有用(10.14)。 - hnh

0
我遇到了同样的问题,并注意到在调用reloadData的行中,实际的自动布局约束丢失了。我的(hacky)解决方案是手动添加应该自动设置为单元格的约束。请注意,在我的表视图中,我只使用一列,因此我可以将宽度约束设置为等于行的宽度,而不是依赖于指定的列宽度。
class CustomRowView: NSTableRowView {
  override func addSubview(_ view: NSView) {
    super.addSubview(view)

    // Add constraints NSTableView is supposed to set up
    view.topAnchor.constraint(equalTo: topAnchor).isActive = true
    view.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
    view.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
    view.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 1.0).isActive = true
    view.layoutSubtreeIfNeeded()
  }
}

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