NSOutlineView expandItem() no-op? (Swift 3)

I have a Swift 3 application I've been working on, and just implemented an NSOutlineView. The data is there and all is working, but...


Attempting to call any of the following methods on the outlineView has no effect:


outline.expandItem(item)
outline.expandItem(item, expandChildren: false)
outline.collapseItem(item)
outline.collapseItem(item, collapseChildren: false)


I've confirmed that all the references are correct, but absolutely nothing happens. Clicking the disclosure arrow does expand and collapse the appropriate item, so the data is correct and the outline view is rendering properly... I just cannot expand/collapse anything programmatically.


I've attempted to step through the calls with the debugger, and when I try to step into the expandItem() call, it simply steps over as if the call is nil.


Anyone else encounter this? I'd be pulling my hair out if I had any left...

Accepted Reply

ARGHHH. Just figured it out.


My "Section" struct is being passed by value, but despite the typing of item as Any, NSOutlineView is comparing by reference, of course. Duh!


Switched it to a class for reference semantics and all is right.

Replies

Have you implemented the "outlineView:isItemExpandable:" data source method? It's marked optional, but it really isn't.


FWIW it's normal that expandItem is annoying to use. There's always some detail that gets overlooked and prevents it from doing anything. All you can do is futz around until you figure out what you've missed.

Thanks Quincey,


I did implement that method, and the "normal" outline view UI works fine (ie: toggling the disclosure triangles via the cursor). What I ultimately want to do is to have the "sections" not have the discolsure triangle, but have the whole item triggle the expand/collapse, much like the "source list" style. Then within the sections, certain files which represent library collections should have disclosure triangles to expand/collapse their sub-items.


FWIW, here is my DataSource/Delegate implementation:


extension ProjectViewController: NSOutlineViewDataSource {
  
    public func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
        if let section = item as? Section {
            return section.files.count
        }
        else {
            return allSections.count
        }
    }
  
    public func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
        if let section = item as? Section {
            return section.files[index]
        }
        else {
            return allSections[index]
        }
    }
  
    public func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
        if isProjectSection(item: item) {
            return false
        }
        else if item is Section {
            return true
        }
        else {
            return item is LibraryFile
        }
    }
}

extension ProjectViewController: NSOutlineViewDelegate {

    public func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
        if let item = item as? Section {
            let view = outlineView.make(withIdentifier: item.identifier, owner: nil)
            if isProjectSection(item: item) {
                (view?.subviews.filter { $0 is NSTextField }.first as? NSTextField)?.stringValue = project?.name ?? "Untitled"
            }
            else if let headerBtn = (view?.subviews.filter { $0 is Button })?.first as? Button {
                headerBtn.target = self
                headerBtn.action = #selector(toggleSectionHeader(sender:))
                headerBtn.associatedObject = item
            }
            return view
        }
        else if let item = item as? RepresentsAFile {
            let view = outlineView.make(withIdentifier: "FileItemCell", owner: nil)
            (view?.subviews.filter { $0 is NSTextField }.first as? NSTextField)?.stringValue = item.name
            return view
        }
        else {
            return nil
        }
    }
}



And this is the method where I'm trying to call expand/collapse:

extension ProjectViewController {

    func toggleSectionHeader(sender: AnyObject?) {
        guard let section = (sender as? Button)?.associatedObject as? Section else { return }
       
        if projectItems.isItemExpanded(section) {
            projectItems.collapseItem(section)
        }
        else {
            projectItems.expandItem(section)
        }
    }
}


It's notable that the isItemExpanded() call works correctly, and returns as expected based on the expanded state of the particular section.

ARGHHH. Just figured it out.


My "Section" struct is being passed by value, but despite the typing of item as Any, NSOutlineView is comparing by reference, of course. Duh!


Switched it to a class for reference semantics and all is right.