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.