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

5 Answers 5

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)
    }
}
4
  • 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, 2016 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. Jul 1, 2016 at 8: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, 2017 at 8: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 4, 2020 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
}
2

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

1
  • 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, 2015 at 21:59
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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.