0

i am using a view based NSTableView with a column that shows dates, and the table cell views use a shared DateFormatter.

let view: NSTableCellView? = tableView.makeView(withIdentifier: column.identifier, owner: self) as! NSTableCellView?
let entry = (logController.arrangedObjects as! [LogEntry])[row]
    switch column.identifier {
    case columnDateKey:
        view?.textField?.formatter = sharedDateFormatter
        view?.textField?.objectValue = entry.date

The application has a user preference to choose the date format and previously the code

tableView.setNeedsDisplay(tableView.rect(ofColumn: tableView.column(withIdentifier: columnDateKey)))

would refresh the column with the new date format.

With macOS Mojave this does not happen. Investigation shows that although the drawRect: is called for the underlying TableView there are no calls made to tableView(:viewFor:row:) to obtain the new values for table cell views. Calling tableView.reloadData(forRowIndexes:columnIndexes:) does result in calls to tableView(:viewFor:row:) but the display does not refresh (although it does for tableView.reloadData()).

Any external cause to redraw e.g. selecting a row correctly updates that area alone. The other thing I've seen is that with a long table slowly scrolling up will eventually result in the new format appearing although existing cells do not change when scrolled back to until scrolled a long way past before returning. This would seem to infer that there are cached views that are not considered to have changed when only the configuration of the attached formatter changes (although are when the value of the contents changes)

This behaviour changed with the introduction of Mojave and I am finding it difficult to believe that no-one else has reported it and so am beginning to question my original code. Am I missing something?

The following test code demonstrates the problem, the "View requested" message is not printed for variants of setNeedsDisplay and display is only redrawn for reloadData()

styleButton is tick box to toggle number format and refreshButton is action button to request a redraw

Setting the value to a random value will result in expected redraw behaviour

import Cocoa

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {

    @IBOutlet weak var window: NSWindow!
    @IBOutlet weak var table: NSTableView!
    @IBOutlet weak var styleButton: NSButton!
    @IBOutlet weak var refreshButton: NSButton!
    @IBOutlet weak var testView: NSView!

    let numberFormatter = NumberFormatter()

    func applicationWillFinishLaunching(_ notification: Notification) {
    numberFormatter.numberStyle = symbolButton.state == NSControl.StateValue.on ? NumberFormatter.Style.decimal : NumberFormatter.Style.none
    }

    @IBAction func refresh(sender: Any?) {
        numberFormatter.numberStyle = styleButton.state == NSControl.StateValue.on ? NumberFormatter.Style.decimal : NumberFormatter.Style.none
        table.setNeedsDisplay(table.rect(ofColumn: 0))
        // table.needsDisplay = true
        // table.reloadData(forRowIndexes: IndexSet(integersIn: 0..<table.numberOfRows), columnIndexes:[0])
        // table.reloadData()
    }
}

extension AppDelegate: NSTableViewDataSource {
    func numberOfRows(in tableView: NSTableView) -> Int {
        if tableView == table {
            return 40
        }
        return 0
    }
}

extension AppDelegate: NSTableViewDelegate {
    func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
        print("View requested")
        guard tableColumn != nil else {
            return nil
        }
        let column = tableColumn!
        if tableView == table {
            let view: NSTableCellView? = tableView.makeView(withIdentifier: column.identifier, owner: self) as! NSTableCellView?
            view?.textField?.formatter = numberFormatter
            view?.textField?.objectValue = 123.456
            return view
        }
        return nil
    }
}
dratsie
  • 1
  • 3
  • Try testing that your optionals are returning values? Maybe something's changed elsewhere? Also, you've updated to 10.14. Did you also update to Xcode 10.2.1 and therefore Swift 5? – Ron Apr 22 '19 at 14:18
  • Fully updated however a version of application built on High Sierra shows problem when run on Mojave – dratsie Apr 22 '19 at 16:29
  • I suggest you start with breakpoints to see that whatever's changed in Mojave doesn't return nil for any optional properties that used to contain values. I suspect them because the behavior you're seeing is no action instead of a crash. – Ron Apr 22 '19 at 16:40
  • Thank you. However with reference to the test code I've added, following a call to func refresh there is no subsequent interaction with the application when using a variant of setNeedsDisplay, and rectangle is valid as p table.rect(ofColumn: 0) gives (NSRect) $R2 = (origin = (x = 0, y = 0), size = (width = 101, height = 1680)) – dratsie Apr 22 '19 at 16:58
  • Did you try the test code on High Sierra? – Willeke Apr 22 '19 at 23:27
  • See [setNeedsDisplay does not trigger drawRect in subviews as expected](https://stackoverflow.com/questions/11480341/setneedsdisplay-does-not-trigger-drawrect-in-subviews-as-expected) – Willeke Apr 22 '19 at 23:28
  • Setting the object value of `NSTextField` doesn't trigger a redraw if the value doesn't change. – Willeke Apr 22 '19 at 23:33
  • That link answers my problem. Call has seemed to redraw subviews for several years and has mislead me to look at the table rather than your second point, which I had noted but considered secondary. Useful to have that confirmed as well. Many thanks for the reply – dratsie Apr 25 '19 at 19:42

1 Answers1

0

Incorrectly relying on view.setNeedsDisplay to automatically update subviews. This is not the case (although had appeared to work that way, previously) - refer comment from Willeke above

dratsie
  • 1
  • 3