9
votes

I have an NSTableView whose first row is pushed down 10 pt from the top. Headers are turned off, there are no group rows, cell spacing is 0, and the enclosing scroll view's content inset is 0.


UPDATED

Here's a sample project that demonstrates the issue.


Here's a grab of the view hierarchy debugger:

enter image description here

Here's the vertical constraints of the first row's NSTableRowView while paused in the view hierarchy debugger:

enter image description here

I tried implementing the delegate's tableView(_:didAdd:forRow:) and inspecting the constraints, first with constraintsAffectingLayout(for:):

[<NSLayoutConstraint:0x60000390bd40 'NSTableRowView_Encapsulated_Layout_Height' NSTableRowView:0x7fdb12e26eb0.height == 24 priority:500   (active)>]

Then printing all the row view's constraints:

  - 0 : <NSLayoutConstraint:0x60000390bcf0 'NSTableRowView_Encapsulated_Layout_Width' NSTableRowView:0x7fdb12e26eb0.width == 329 priority:500   (active)>
  - 1 : <NSLayoutConstraint:0x60000390bd40 'NSTableRowView_Encapsulated_Layout_Height' NSTableRowView:0x7fdb12e26eb0.height == 24 priority:500   (active)>
  - 2 : <NSAutoresizingMaskLayoutConstraint:0x60000390bbb0 h=--& v=-&- InlineCell.minX == 16   (active, names: InlineCell:0x7fdb12e283a0, '|':NSTableRowView:0x7fdb12e26eb0 )>
  - 3 : <NSAutoresizingMaskLayoutConstraint:0x60000390bc00 h=--& v=-&- InlineCell.width == 297   (active, names: InlineCell:0x7fdb12e283a0 )>
  - 4 : <NSAutoresizingMaskLayoutConstraint:0x60000390bc50 h=--& v=-&- InlineCell.minY == 0   (active, names: InlineCell:0x7fdb12e283a0, '|':NSTableRowView:0x7fdb12e26eb0 )>
  - 5 : <NSAutoresizingMaskLayoutConstraint:0x60000390bca0 h=--& v=-&- V:[InlineCell]-(0)-|   (active, names: InlineCell:0x7fdb12e283a0, '|':NSTableRowView:0x7fdb12e26eb0 )>

The cell's minY constraint is set to 0, but the row is using an autoresizing mask. The table uses a simple diffable datasource:

NSTableViewDiffableDataSourceReference(tableView: table) { tableView, column, row, item in
    guard let cell = tableView.makeView(withIdentifier: inlineCellIdentifier, owner: self) as? NSTableCellView else { 
        preconditionFailure("Failed to create results cell") 
    }
    cell.textField?.textColor = self.themeAttributes.color
    cell.textField?.font = self.themeAttributes.font
    cell.textField?.stringValue = self.displayString(for: item)
    return cell
}

The only delegate method implemented is tableView(_:heightOfRow:). The table aligns itself with the lines of a sibling text view so it gets it row height from there:

func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat {
    guard let layoutManager = editor?.layoutManager else { 
        preconditionFailure("Missing layout manager in editor") 
    }
    return height(of: row, in: layoutManager)
}

This seems like it's probably obvious, but I don't see why the table is forcing its rows to be offset like this. I've found plenty of questions on this forum asking how to insert a gap at the top of the table, but not how to remove one. Any advice?

1
Could cell spacing be the culprit? (You have my fullest sympathies. I am currently wrangling autolayout with NSOutlineView, which inexplicably insists that one of my rows has a height constraint of 0...)green_knight
Unfortunately no. Cell spacing is 0, and I wouldn’t expect it to apply above the first row, right?smr
Anything is possible in autolayout land. I can't reproduce this right now - you should edit your post to make it clearer that this is a MacOS 11 issue. (Diffable Data sources is an announcement I've missed at WWDC; thanks for pointing them out). If it persist, you might want to file a bug for this.green_knight
One more thing - if you'd like to get more people involved, I'd recommend to create a MRE one can download and play with it/check it, ...zrzka
Thanks for the advice. @green_knight: not convinced this is a Big Sur issue, but as zrzka points out, I should have produced an example project which would be a way to check that. I'll get on it--thanks for keeping me on the straight and narrow :)smr

1 Answers

18
votes

It's not a bug, as stated in some comments, it's a new way how the NSTableView works in Big Sur (actually there's a bug, but elsewhere, see below). Free Pascal site contains nice overview of what's new.

New NSTableView properties

macOS Big Sur introduced new NSTableView properties:

style documentation:

The default value for this property is NSTableView.Style.automatic in macOS 11. Apps that link to previous macOS versions default to NSTableView.Style.fullWidth.

effectiveStyle documentation:

If the style property value is NSTableView.Style.automatic, then this property contains the resolved style.

.automatic documentation:

The system resolves the table view style in the following manner:

  • If the table view is in a sidebar split-view controller item, effectiveStyle resolves to NSTableView.Style.sourceList.
  • If the table’s scroll view has a border, effectiveStyle resolves to NSTableView.Style.fullWidth.
  • Otherwise effectiveStyle resolves to NSTableView.Style.inset. However, if the table needs extra space to fit its column cells, effectiveStyle resolves to NSTableView.Style.fullWidth.

Your table view has style set to .automatic in the Main.storyboard. Which means that the effective style resolves to .inset -> 10pt around content (based on the rules from the .automatic documentation).

Open the view debugger with a selected row. You can see the .inset style effect:

enter image description here

Use .fullWidth to remove 10pt insets.

You can also test the .automatic style behavior - try to add a border to the scroll view. Resolves to .fullWidth.

Bug & workaround

You probably tried to set the table view style to Full Width in the Interface Builder. It doesn't work. No matter what value you choose, it behaves like .automatic.

Add the following line to your code to workaround this issue:

tableView?.style = .fullWidth

Works as expected now:

enter image description here

Reported as FB8258910.