10

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?

zrzka
  • 20,249
  • 5
  • 47
  • 73
smr
  • 890
  • 7
  • 25
  • 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 Aug 02 '20 at 23:36
  • Unfortunately no. Cell spacing is 0, and I wouldn’t expect it to apply above the first row, right? – smr Aug 03 '20 at 04:29
  • 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 Aug 03 '20 at 08:24
  • One more thing - if you'd like to get more people involved, I'd recommend to create a [MRE](https://stackoverflow.com/help/minimal-reproducible-example) one can download and play with it/check it, ... – zrzka Aug 03 '20 at 08:26
  • 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 Aug 04 '20 at 00:55
  • Updated with example project. Can only check on macOS 11 since it uses the new `NSTableViewDiffableDataSourceReference` – smr Aug 04 '20 at 01:37
  • Revised using `NSTableViewDataSource` for Catalina compatibility and ran it on another machine on 10.15 and darn if the problem doesn't go away. Didn't think this was a beta bug, but looks like @green_knight was right! – smr Aug 04 '20 at 02:49

1 Answers1

19

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.

zrzka
  • 20,249
  • 5
  • 47
  • 73
  • 1
    Didn’t mention it, but I did play with the style without effect. Didn’t think of doing it in code, though. Brilliant, sir! – smr Aug 04 '20 at 11:08
  • 2
    Thanks for the detailed explanation. – green_knight Aug 04 '20 at 21:39
  • Update for Xcode (12.0 beta 4 12A8179i) & macOS 11.0 Beta 4 20A5343i. 1) Style in Attributes inspector is still sort of ignored (behaves like `.automatic`, interestingly the only option which is not ignored is _Source List_). 2) Setting type in the code is still the most reliable way. 3) There's a new bug - `.inset` type crops bottom of the selection background. – zrzka Aug 05 '20 at 13:11
  • 1
    There's also `.plain` style for "no insets, padding or any other kind of decoration applied". It's useful for apps that need to maintain the legacy view. – Ivan Mir Dec 16 '20 at 00:18
  • 2
    Hmnm, iit looks like there's no public API for querying the inset size. I found out that NSTableView has a private method called `_styleContentInsets()` which returns `NSEdgeInsets`. On Big Sur, the result is an inset of top: 5, bottom: 10, and left/right: 16 – Alexander Apr 20 '21 at 01:54