0

I'm working with an application were I'm wanting to combine all cells in an NSTableView to act as a section header for rows below it. One solution I'm working with is to use - (BOOL)tableView:isGroupRow: which works somewhat, but unfortunately the formatting that I am using on the NSTextField gets clobbered when using isGroupRow, the text should be white and the bold appears regardless of whether I set it or not.

Clobbered Formatting - Should be white and bold appears regardless

In the sample code if you uncomment out the line // return false in tableView(_ tableView: NSTableView, isGroupRow row: Int) you'll see the correct formatting working fine (except that of course the field shows up twice since it's not being converted to a group row

Formatting is working just fine if you disable isGroupRow

The solution takes several steps and a full sample project can be found at https://github.com/jcnolan/NSTableViewIsGroupRowTest

Here is my tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?

    func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
    
    var text: String = "Doh!"
    var aText: NSAttributedString? = nil
    var cellIdentifier: CellIdentifiers? = nil
    
    guard let item = tableData?[row] else { return nil }
    
    if let tableColumn = tableColumn { cellIdentifier = CellIdentifiers(rawValue: String(describing: tableColumn.identifier.rawValue)) }
    else                             { cellIdentifier = CellIdentifiers.pageCell } // When "groupRow" is true there is no column, so use first column whatever that is

    if let _ = item.title { cellIdentifier = CellIdentifiers.titleRow } // hack
    
    guard let cellIdentifier = cellIdentifier else { return nil }
    
    if let cell = tableView.makeView(withIdentifier: cellIdentifier.uiiId, owner: nil) as? NSTableCellView {
        
        switch cellIdentifier {
        
        case .pageCell:     text = item.page?.description ?? "page unk"; break
        case .commentCell:  text = item.comment ?? "comment unk"; break
            
        case .titleRow:     text = item.title ?? "title unk"
                            var attributes = [NSAttributedString.Key: AnyObject]()
                            attributes[.foregroundColor] = NSColor.white
                            attributes[.font] = NSFont.boldSystemFont(ofSize: 13.0)
                            aText = NSAttributedString(string: text, attributes: attributes)
        }
        
        if let aText = aText { cell.textField?.attributedStringValue = aText }
        else                 { cell.textField?.stringValue = text }
        
        return cell
    }
    return nil
}

And my tableView(_ tableView: NSTableView, isGroupRow row: Int)

func tableView(_ tableView: NSTableView, isGroupRow row: Int) -> Bool {
    
    // return false  // Uncomment this to see NSAttributedString working in standard row
    
    guard let item = tableData?[row], let title = item.title else { return false }
    return true // only if item exists and it's a tittle
}

The solution also requires func tableView(_ tableView: NSTableView, rowViewForRow row: Int)

func tableView(_ tableView: NSTableView, rowViewForRow row: Int) -> NSTableRowView? {
    
    // Required for changing table highlighting
    
    guard let item = tableData?[row] else { return nil }
    
    let retVal:TintedTableRowView? = TintedTableRowView()
    
    if let retVal = retVal {
        retVal.isTitleRow = item.title != nil
    }
    
    return retVal
}

class TintedTableRowView: NSTableRowView {

var isTitleRow:  Bool = false

override func draw(_ dirtyRect: NSRect) {
   
    if self.selectionHighlightStyle != .none || true {
        
        var bgcol:NSColor = NSColor.clear
        
        switch isTitleRow {
        case true: bgcol = NSColor.lightGray
        default:   bgcol = NSColor.clear }
        
        let selectionRect = NSInsetRect(self.bounds, 0, 0)
        bgcol.setStroke()
        bgcol.setFill()
        let selectionPath = NSBezierPath.init(roundedRect: selectionRect, xRadius: 0, yRadius: 0)
        selectionPath.fill()
        selectionPath.stroke()
    }
}

}

Based on the fact that the font gets bolded automatically I'm guessing there is another path needed to tell Swift how to format a group row? I've been struggling with this problem for several days now... any help would be greatly appreciated.

EDIT: Checking the documentation there is a reference "When configured as a source list style table view, rows identified as group rows draw with a specific style unique to source lists." However, I wasn't using the "source list" style and changing it explicitly to "plain" in both the storyboard editor and via code seems to have no effect.

EDIT2: An additional request was made for the data set... though I'm pretty sure this is related to isGroupRow and the full project can be seen at https://github.com/jcnolan/NSTableViewIsGroupRowTest. Here is the data set and viewDidLoad():

struct TableDataItem {
       var title: String? = nil
       var page: Int? = nil
       var comment: String? = nil
   }     

override func viewDidLoad() {
        
        super.viewDidLoad()

        // Do any additional setup after loading the view.
        tableView.delegate = self
        tableView.dataSource = self
        tableView.floatsGroupRows = false
        
        installStubData()
        tableView.reloadData()
    }
    
    func installStubData() {
        
        tableData = []
        tableData!.append(TableDataItem(title: "This is doc title 1", page: nil, comment: nil))
        tableData!.append(TableDataItem(title: nil, page: 1, comment: "this is doc 1, page one comment"))
        tableData!.append(TableDataItem(title: nil, page: 2, comment: "this is doc 1, page two comment"))
        tableData!.append(TableDataItem(title: nil, page: 3, comment: "this is doc 1, page three comment"))
        tableData!.append(TableDataItem(title: "This is doc title 2", page: nil, comment: nil))
        tableData!.append(TableDataItem(title: nil, page: 1, comment: "this is doc 2, page one comment"))
        tableData!.append(TableDataItem(title: "This is doc title 3", page: nil, comment: nil))
        tableData!.append(TableDataItem(title: nil, page: 1, comment: "this is doc 3, page one comment"))
        tableData!.append(TableDataItem(title: nil, page: 2, comment: "this is doc 3, page two comment"))
    }
Jc Nolan
  • 450
  • 4
  • 15
  • This is Mac OS, right? You might want to add that to your title. I work mostly in iOS these days, and my Mac OS table view skills are kinda rusty. You might want to try to draw the attention of folks actively working with MacOS. – Duncan C Sep 12 '21 at 20:06
  • 1
    Thanks @DuncanC, NSAttributedString and Cocoa imply MacOS vs iOS but I added the reference to the title as you suggested in any case - thx – Jc Nolan Sep 12 '21 at 20:51
  • Actually NSAttributed string is shared with iOS. NSTableView and NSTableCellView are MacOS specific, but I missed that when I first saw your question. That's why I suggested adding MacOS to the title. The more people with relevant experience who's attention you can grab, the more likely you are to get a good answer. – Duncan C Sep 12 '21 at 21:01
  • I don't think anyone can answer your question since you don't show your dataset. Your dataset should include a boolean value, though. – El Tomato Sep 13 '21 at 00:33
  • I don't work on macOS, only iOS, so it's unclear. When you have an issue, which `switch case` is called exactly? You only set the `attributedStringValue` when `.titleRow`, not the others. – Larme Sep 13 '21 at 08:06
  • @Larme - The code pattern is a little obtuse as it's designed to allow for either a formatted or unformatted string to come through. In this case it is simplified in that .titleRow is the only one that generates the (formatted) NSAttributedString. In the other cases (the independent .pageCell / .commentCell) it allows for the table's defaullt formattting – Jc Nolan Sep 13 '21 at 15:55
  • Thanks @ElTomato, the issue most likely has to do with how Swift handles isGroupRow and a full sample of this has been posted at https://github.com/jcnolan/NSTableViewIsGroupRowTest but I'm happy to post the data as well if that would be helpful – Jc Nolan Sep 13 '21 at 15:57
  • It's up to you as to how to use table view's `isGroupRow`. I didn't have trouble four years ago. Your dataset does not appear to include a boolean value telling which record is for the table header. – El Tomato Sep 13 '21 at 23:37
  • Thanks @ElTomato, the code checking for the title row is pretty clear and is a simple switch statement in the tableView::ViewFor code: switch cellIdentifier { case .titleRow: text = item.title ?? "title unk" var attributes = [NSAttributedString.Key: AnyObject]() attributes[.foregroundColor] = NSColor.white attributes[.font]=NSFont.boldSystemFont(ofSize: 13.0) aText = NSAttributedString(string: text, attributes: attributes) I'm not sure how much clearer I can make things. – Jc Nolan Sep 16 '21 at 17:12

0 Answers0