NSTableView reloadData(forRowIndexes:columnIndexes :)中断自动布局

时间:2018-06-25 04:45:53

标签: macos autolayout nstableview appkit

我有一个NSTableView,可以根据行的数据值在不同的单元格视图中交换。当模型更改时,我将重新加载表,表的委托将为新数据提供正确的表单元格视图。

该表将自动布局用于其单元格视图。最初,所有单元格视图均正常加载。在更改模型后更新表时,根据调用reloadData()还是reloadData(forRowIndexes:columnIndexes)的不同,结果会有所不同。使用reloadData()时,将加载单元格视图,并且自动布局可以正常工作。如果使用reloadData(forRowIndexes:columnIndexes),则自动布局会产生完全不同的意外结果。

我创建了一个示例项目来演示该问题。 Here是项目设置的图像,包括在表格单元格视图上设置的约束。有两行模板,一个具有蓝图(偶数行),一个具有绿色(奇数行),它们应跨表宽度(减去一些填充)。控制器提供单元格视图:

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

界面中的按钮仅交换第1行的数据并重新加载数据。初始视图看起来像this(交替显示绿色和蓝色矩形)。如果使用reloadData(),则看起来像this(第1行从蓝色更改为绿色)。但是,如果使用reloadData(withRowIndexes:columnIndexes:),则像其他单元格一样,单元格视图缩小到40点宽,反之为480。 Here's的视图调试器抓取,显示了具有错误尺寸的单元格视图并显示了不明确的宽度约束(使用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()
    }
}

2 个答案:

答案 0 :(得分:0)

我认为我已经做好了。如果我在返回之前在单元格上调用layoutSubtreeIfNeeded()(这样就已经设置了它的所有子视图(如动态文本)),那么它似乎可以工作。

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

  cell.layoutSubtreeIfNeeded()
  return cell
}

我希望有帮助。

答案 1 :(得分:0)

我遇到了同样的问题,并注意到对于需要reloadData的行,缺少实际的自动布局约束。我(骇人听闻的)解决方案是添加应该手动为单元自动设置的约束。请注意,在表格视图中,我只使用一列,因此我可以将宽度约束设置为等于行的宽度,而不必依赖于指定的列宽度。

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