tableview reloadData() has bizarre behavior

I have had this question on stackoverflow for a couple days now and no one there can figure it out, so I thought I would try here:

I have a tableView with some custom cells. The columns are sortable with NSSortDescriptor. But the tableView.reloadData() does not refresh the custom cells.

[code]

extension DeviceListViewController:NSTableViewDataSource, NSTableViewDelegate{

    func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView?{

        var result = tableView.makeView(withIdentifier: (tableColumn?.identifier)!, owner: self) as! NSTableCellView

        if (tableColumn?.identifier)!.rawValue == "Name" {

            result.textField?.stringValue = homedevices[row].name

            result.imageView?.image = homedevices[row].image

        } else if (tableColumn?.identifier)!.rawValue == "DeviceGroup" { 

            let buttoncell = result as! ButtonCellView

            buttoncell.selectedItem = homedevices[row].button

            result = buttoncell

        } else if (tableColumn?.identifier)!.rawValue == "Brightness" {

            let slidercell = result as! BrightnessSliderCellView

            slidercell.sliderValue = homedevices[row].brightness result = slidercell

        }

        return result

    }

    func tableView(_ tableView: NSTableView, sortDescriptorsDidChange oldDescriptors: [NSSortDescriptor]) {

        let dataArrayMutable = NSMutableArray(array: homedevices)

        dataArrayMutable.sort(using: tableView.sortDescriptors)

        homedevices = dataArrayMutable as! [HomeDevice] tableView.reloadData()

    }

}

[/code]

I am not sure why this doesn't work, but sorting any column results in the custom cells looking exactly the same until I close the window and reopen it. How can I fix this? Or is there a way to do a full reload of the view as if closing and reopening the window?

EDIT: In case this is confusing, I will try to rephrase it. I would like to be able to reload the data in the tableView for all of the cells that are in the table. Currently, I have to close the window and reopen it to refresh all of the table cells.

DAY2: The lastest attempt involves doing something like this:

[code]

let indexSet = IndexSet(integersIn: 0..<homedevices.count)

tableView.removeRows(at: indexSet, withAnimation: .effectFade)

tableView.reloadData()

[/code]

now, all the cells refresh, but the sorts are still messed up sometimes. I can actually make this kind of work now, with all columns displaying correct data, but only if I only implement sort on one column. If I use multiple columns for sorting, it still doesn't work right. If anyone has any idea of how to sort an NSTableView with sub-classed cells in Swift 4, please let me know a better direction.  This is so strange.

You're going to have to narrow the scope of your investigation. There's too much going on here to be able to reason out the solution.


I would start by tracking what happens at the moment you call "reloadData". After that, the table view can be expected to ask for the number of rows, then invoke "tableView(_:viewFor:row:)" once for each row. Do you see the correct number of cells (re-) created? Is the "homedevices[row]" value correct for the row each time? When you say "the custom cells looking exactly the same", what does that mean? After all, the cell for a "homedevices" element is going to look the same wherever it's sorted, isn't it? So you mean the rows appear to be in the original order? What happens if you select some or all of them? (Selecting rows should cause them to be redrawn, so you can sometimes tell whether you're looking at a display artifact or a data artifact that way.)


A couple of other things:


— How are you defining "custom cells"? You have custom subclasses, but are the cells themselves defined in the table in IB, or are you using separate NIB files? Are you registering the identifier or XIB file name? (If the cells are designed into the table, you must not also register them.)


— Are you using a NSArrayController at all? Are you using any bindings?


— Can you reproduce this behavior by reloading the table without changing the sort descriptors?


— Are you certain you're doing the reloadData on the main thread?

What I mean is that I have a subclass that inherits NSTableCellView. I have a few cells that do something like this:


class BrightnessSliderCellView: NSTableCellView {

var sliderValue: Int = -1

@IBOutlet weak var brightnessSlider: NSSlider!


override func draw(_ dirtyRect: NSRect) {

super.draw(dirtyRect)

if sliderValue < 0 { brightnessSlider.isEnabled = false } else { brightnessSlider.isEnabled = true }

brightnessSlider.integerValue = sliderValue

}

}


I can sort my homedevices array and do a tableView.reloadData() and see via console logs that the sliderValue for each row in the table is getting the correct sorted data. But the sliders do not change, ie: they stay the same value, and stay disabled/enabled if they were like that on inital load. I can see the other columns that do not contain subclassed NSTableCellView cells change when sorted. The only way that I can see that the sorting worked on these subclassed cells is by closing the window and reopening. I don't understand what is going on. I have not implemented NSArrayController. I don't have NIB files, but have a slider binding from storyboard.

Accepted Answer

Your "draw" method is suspect, because it does more than drawing. Specifically, it changes the state of the "brightnessSlider" subview (at least, I assume this control is actually a subview of the BrightnessSliderCellView). That state change triggers redrawing the subview and that in turn may normally trigger redrawing of the parent view. However, because the view machinery is inside a draw already, the redrawing trigger may not have the intended effect (or any effect).


I'm not sure of my logic here, but in general a "draw" method should do nothing but actually draw. Things like the adjustment of controls should happen outside that. It's also possible that these cell views exist in a view hierarchy where they have been accidentally layer-backed (the top checkboxes in the last tab of the IB inspector). I've seen cases of that, where the layer backing causes redrawing to misbehave.


Also, I don't quite understand what "a slider binding from storyboard" means. Are you talking about a binding for the slider value? (If so, why, since you set the values manually?) I asked about bindings because of the possibility that you're binding control values to properties that might not be getting updated KVO-compliantly.


If you can't make any progress, is there any chance you can reduce the problem to a test project that you can post somewhere?

Thank you so much for responding to my post. You were right about the accidentally layer-backed thing. One of the cells had a box checked. Taking this off fixed everything! wow! Thanks again for your help!

tableview reloadData() has bizarre behavior
 
 
Q