3

I'm running into the strangest issue with my NSOutlineView:

  • Everything is set up in a storyboard, i.e. the outline view and two NSTableCellViews
  • The two cell views are mostly the same, only one shows an icon, the other one doesn't
  • I can begin editing an item (row) by pressing the Return key, i.e. the NSTextField that is part of my NSTableCellView enters into edit mode. This is the default behavior and works fine so far.

However:

  • After editing ends and the item was changed, I use the second table view cell from the storyboard (the one without an icon).
  • Now entering "edit" mode by pressing the Return key does not work anymore! The app beeps and that's it.

Both cell views on their own are editable when they are initially loaded. I confirmed this. The problem occurs when an item is first shown with one table cell view, then with the other one (and vice versa).

Editing an item multiple times works fine then the underlying NSTableCellView does not change. Can anybody shine some light at what is going on here?

To reproduce:

  1. New Xcode project, Mac app
  2. Add an NSOutlineView to your view controller with one column
  3. Add two table view cells, "One", "Two" (identifiers)
  4. Use the code below

Steps:

  1. Run the app, select the second item
  2. Press "Return", type "green", "Return"
  3. Press "Return" again to enter edit mode a second time

Expected:

  • Cell should become editable again

Actual:

  • App beeps

NSOutlineView

Code:

import Cocoa

class ViewController: NSViewController, NSOutlineViewDataSource, NSOutlineViewDelegate, NSTextFieldDelegate {

    @IBOutlet var outlineView: NSOutlineView!

    private var items = ["One", "Two"]

    func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
        return item == nil ? items.count : 0
    }

    func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
        return items[index]
    }

    func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
        return false
    }

    func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
        guard let item = item as? String else { return nil }

        NSLog("Vending cell view for: \(item)")

        let identifier = item.contains("green") ? "Two" : "One"

        if let view = outlineView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: identifier), owner: nil) as? NSTableCellView
        {
            view.textField?.stringValue = item

            view.textField?.delegate = self

            return view
        }
        return nil
    }

    func controlTextDidEndEditing(_ obj: Notification) {
        NSLog("Did end editing")

        guard
            let textField = obj.object as? NSTextField,
            let item      = outlineView.item(atRow: outlineView.row(for: textField)) as? String else {
            return
        }

        NSLog("Reloading item: \(item)")

        let row = outlineView.row(for: textField)

        items[row] = textField.stringValue

        outlineView.reloadItem(item)
    }
}

UPDATE:

I found a workaround, but this looks like a bug to me. If I call outlineView.reloadData(forRowIndexes:columnIndexes:) right after reloadItem(), the problem does not occur.

Doesn't work (problem occurs):

outlineView.reloadItem(item)

Doesn't work (outlineView's data model not updated, shows old value):

outlineView.reloadData(forRowIndexes: IndexSet([row]), columnIndexes: IndexSet([0]))

This finally works:

outlineView.reloadItem(item)
outlineView.reloadData(forRowIndexes: IndexSet([row]), columnIndexes: IndexSet([0]))

The above causes the cell view to be requested a second time.

Mark
  • 6,647
  • 1
  • 45
  • 88

0 Answers0