NSTableView has unwanted space above first row
Asked Answered
C

1

11

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?

Crusado answered 30/7, 2020 at 14:19 Comment(7)
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...)One
Unfortunately no. Cell spacing is 0, and I wouldn’t expect it to apply above the first row, right?Crusado
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.One
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, ...Normie
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 :)Crusado
Updated with example project. Can only check on macOS 11 since it uses the new NSTableViewDiffableDataSourceReferenceCrusado
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 @One was right!Crusado
N
21

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.

Normie answered 4/8, 2020 at 7:54 Comment(5)
Didn’t mention it, but I did play with the style without effect. Didn’t think of doing it in code, though. Brilliant, sir!Crusado
Thanks for the detailed explanation.One
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.Normie
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.Meshwork
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: 16Eugenie

© 2022 - 2024 — McMap. All rights reserved.