Skip to content

Instantly share code, notes, and snippets.

@raphaeltraviss
Last active May 21, 2018 20:27
Show Gist options
  • Save raphaeltraviss/3d8755b85d832bf3a69e18fecdddd76c to your computer and use it in GitHub Desktop.
Save raphaeltraviss/3d8755b85d832bf3a69e18fecdddd76c to your computer and use it in GitHub Desktop.
Generic NSOutlineView delegate in Swift. Allows the delegate to be destroyed and re-created cleanly on every state change.
import AppKit
// This class will manufacture a delegate object for use with NSOutlineView,
// given the following:
// - A setup function, that will take an NSTableViewCell or subclass of the
// given type, and fill in its views.
// - A cell identifier from the storyboard, associated to the outline view.
// - A function that is called when a selection is made...
// Since the selection handler doesn't actually provide the selected node,
// you need to seal a reference to your outline view inside your Selection
// Action closure. Yuck. Make sure to use a capture list to prevent memory cycles.
final class OutlineDelegate<CellType, ModelType> : NSObject, NSOutlineViewDelegate {
typealias CellSetup = (CellType, ModelType) -> NSView?
typealias SelectionAction = () -> Void
typealias MoveAction = () -> Void
private let cell_identifier: String
private let configure_cell: CellSetup
private let selectionAction: SelectionAction
init(cell_identifier: String,
configure_cell: @escaping CellSetup,
selection_action: @escaping SelectionAction)
{
self.cell_identifier = cell_identifier
self.configure_cell = configure_cell
self.selectionAction = selection_action
}
func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
let view = outlineView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: cell_identifier), owner: self) as! CellType
return configure_cell(view, item as! ModelType)
}
func outlineViewSelectionDidChange(_ notification: Notification) {
selectionAction()
}
}
/* Code preceeds
This is just an example of how you might use such a delegate
*/
// Temporarily disable state updates, to prevent an state management cycle
// from outlineView.selectRowIndexes().
func new_state(state) {
var outline_state_updates_are_enabled = false
self.stored_delegate = OutlineDelegate<NSTableCellView, MyOutlineNodeClass>(
cell_identifier: "MyOutlineNodeCell",
configure_cell: { cell, node in
cell.textField?.stringValue = "\(node)"
return cell
},
// @TODO: do we really need to capture a reference to the outline view, or just its data?
selection_action: { [weak outline_view = self.outlineView] () -> Void in
guard let outline = outline_view else { return }
guard let selected_node = outline.item(atRow: outline.selectedRow) as? MyOutlineNodeClass else { return }
guard outline_state_updates_are_enabled else { return }
let index = selected_node.state?.stack_index
GLOBAL.uiActionStore.dispatch(StateMutation.DoSomethingAtIndex(index))
}
)
zoneGraphView.delegate = self.stored_delegate
}
/* More code follows */
@raphaeltraviss
Copy link
Author

raphaeltraviss commented May 21, 2018

Instead of capturing an entire reference to the NSOutlineView, it may be possible to only capture a reference to its makeView method. You still should use a capture list, however. Also, remember that delegates and datasources in AppKit are weak references, so you will need to save the re-created delegate/datasource instances to a state variable somewhere, otherwise they will be garbage-collected!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment