4

I am trying to produce a custom header for my NSTableView. I would like to change the font of the header text and remove the borders and vertical separators.

My current top and bottom header is shown below:

enter image description here

Does anyone know how do I do this please?

UPDATE: After applying the fix the header now looks as I intended:

enter image description here

iphaaw
  • 6,764
  • 11
  • 58
  • 83

5 Answers5

6

Actually both the answers from mprudhom and Pronto contributed nicely to the final fix. Many thanks to both of you.

I thought I'd post my final answer so anyone following on can see how I fixed it.

I couldn't use mprudhom's code alone as my table only had a NSTableHeaderView and no TableHeaderCells. Fortunately Pronto came to the rescue on that:

class MyTable: NSTableView, NSTableViewDataSource, NSTableViewDelegate
{
    override func tableView(tableView: NSTableView, viewForTableColumn tableColumn: NSTableColumn?, row: Int) -> NSView?
    {
        tableColumn!.headerCell = MyTableHeaderCell(textCell: tableColumn!.identifier)
    }
}

I used the NSTableHeaderCell code directly from mprudhom:

final class MyTableHeaderCell : NSTableHeaderCell
{
    required init?(coder aDecoder: NSCoder)
    {
        fatalError("init(coder:) has not been implemented")
    }

    override init(textCell: String)
    {
        super.init(textCell: textCell)
        self.font = NSFont.boldSystemFontOfSize(14)
    }

    override func drawWithFrame(cellFrame: NSRect, inView controlView: NSView)
    {
        //super.drawWithFrame(cellFrame, inView: controlView)//, since that is what draws borders
        self.drawInteriorWithFrame(cellFrame, inView: controlView)
    }

    override func drawInteriorWithFrame(cellFrame: NSRect, inView controlView: NSView)
    {
        let titleRect = self.titleRectForBounds(cellFrame)
        self.attributedStringValue.drawInRect(titleRect)
    }
}

Once these two methods were implemented, I had one last problem in that the NSTableHeaderView was drawn without the border as required but did have a grey background. So I overrode the NSTableHeaderView class with this method:

class ForecastHeaderView : NSTableHeaderView
{
    required init?(coder: NSCoder)
    {
        super.init(coder: coder)
    }

    override init(frame frameRect: NSRect) 
    {
        super.init(frame: frameRect)
        self.wantsLayer = true
    }

    override func drawLayer(layer: CALayer, inContext ctx: CGContext)
    {
        super.drawLayer(layer, inContext: ctx)
        layer.backgroundColor = CGColorCreateGenericGray(1.0, 1.0)
    }
}
iphaaw
  • 6,764
  • 11
  • 58
  • 83
  • Where did you use the custom `NSTableHeaderView (ForecastHeaderView)`. Or where did you implement it so that the TableView uses this custom class. – Codey Mar 28 '16 at 21:11
  • When you look at your table view via InterfaceBuilder, you'll see a Scroll View object, a Clip VIew object, the actual Table View and columns inside that. On the same level as the table view is a NSTableViewHeader object. Select that and change the custom type to be "`ForecastHeaderView`", or whatever your custom header view type is. – Michael Dautermann Jul 01 '16 at 08:50
  • I'm wondering if you have any update for this code for Swift 3. I have tried your code, some APIs have changed and it doesn't compile. I've tried to figure it out but since the Layer is being handled differently. I cannot override the drawLayer. I tried to find a workaround by extending the CALayer but I still cannot achieve it. – Amir Jun 12 '17 at 08:16
  • 1
    I have the same problem as @Amir. I am using Swift 5 and this seems to be impossible to achieve on macOS 11. – inexcitus Sep 04 '20 at 16:21
3

One way to do it, in your NSTableViewDelegate's viewForTableColumn -function:

if tableColumn!.identifier == "description" {
    let cell = NSTableHeaderCell()
    cell.title = "New Title"
    cell.backgroundColor = NSColor.redColor()
    cell.drawsBackground = true
    cell.bordered = false
    cell.font = NSFont(name: "Helvetica", size: 16)
    tableColumn!.headerCell = cell
}
Prontto
  • 1,671
  • 11
  • 21
1

You will need to take over the drawing yourself by subclassing NSTableHeaderCell, like so:

final class CustomTableHeaderCell : NSTableHeaderCell {
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override init(textCell: String) {
        super.init(textCell: textCell)
        self.font = NSFont.boldSystemFontOfSize(20)
    }

    override func drawWithFrame(cellFrame: NSRect, inView controlView: NSView) {
        // skip super.drawWithFrame(), since that is what draws borders
        self.drawInteriorWithFrame(cellFrame, inView: controlView)
    }

    override func drawInteriorWithFrame(cellFrame: NSRect, inView controlView: NSView) {
        let titleRect = self.titleRectForBounds(cellFrame)
        self.attributedStringValue.drawInRect(titleRect)
    }
}

If you are setting up the table programmatically, you would do it like:

let col = NSTableColumn(identifier: id)
col.headerCell = CustomTableHeaderCell(textCell: title)
col.title = title
self.dataTableView.addTableColumn(col)

Otherwise, you should be able to just set the name of your class to your subclass' name in Interface Builder.

rustyMagnet
  • 3,479
  • 1
  • 31
  • 41
marcprux
  • 9,845
  • 3
  • 55
  • 72
  • 1
    Thank you. I think I'm just missing one more step. My table only has a NSTableHeaderView and I cannot select the TableHeaderCell to be able to subclass it. – iphaaw Sep 22 '15 at 21:59
1

The above answers works also directly you can use in viewDidLoad

override func viewDidLoad() {
        super.viewDidLoad()   
        tableView.tableColumns.forEach { (column) in
                column.headerCell.backgroundColor = NSColor.white
                column.headerCell.drawsBackground = true
                column.headerCell.isBordered = false
                column.headerCell.attributedStringValue = NSAttributedString(string: column.title, attributes: [NSAttributedString.Key.font: NSFont.systemFont(ofSize: 13)])
              }
}
roshan posakya
  • 1,010
  • 10
  • 14
0

What seems to work in macOS 11:

  1. Set NSTableHeaderCell background

     func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
     tableColumn?.headerCell.drawsBackground = true
     tableColumn?.headerCell.backgroundColor = .orange
     ...
    
  2. Set NSTableView style in IB to Full Width

jvarela
  • 3,744
  • 1
  • 22
  • 43
Tom
  • 56
  • 5