Search code examples
swiftcocoadrag-and-dropnsoutlineview

Spring load NSOutlineView leaf rows


I've subclassed NSOutlineView and implemented NSSpringLoadingDestination, but springLoadingActivated() is only ever called on non-leaf rows, even though I've implemented springLoadingEntered() and springLoadingUpdated() to indiscriminately return [.enabled]. I assume NSOutlineView's built-in drag & drop support is interfering with my attempts. Is there a workaround for this?


Solution

  • Feels a little hacky, but it seems I can get away with this by spring loading the rows, but also overriding all of the NSDraggingDestination methods to pass the word on to the parent NSOutlineView:

    class SpringLoadedOutlineRow: NSTableCellView, NSSpringLoadingDestination {
        var outlineView: NSOutlineView! {
            didSet {
                registerForDraggedTypes(outlineView.registeredDraggedTypes)
            }
        }
    
        //Pass through `NSDraggingDestination` methods so we don't interfere with `NSOutlineView`
        override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation {
            return outlineView.draggingEntered(sender)
        }
        override func prepareForDragOperation(_ sender: NSDraggingInfo) -> Bool {
            return outlineView.prepareForDragOperation(sender)
        }
        override func performDragOperation(_ sender: NSDraggingInfo) -> Bool {
            return outlineView.performDragOperation(sender)
        }
        override func concludeDragOperation(_ sender: NSDraggingInfo?) {
            outlineView.concludeDragOperation(sender)
        }
        override func draggingExited(_ sender: NSDraggingInfo?) {
            outlineView.draggingExited(sender)
        }
    
        //Only pass through if we're expandable & not yet expanded (to get `NSOutlineView`'s default disclosure spring-loading); otherwise it will eat our leaf spring-loading
        override func draggingUpdated(_ sender: NSDraggingInfo) -> NSDragOperation {
            guard let representedItem = outlineView.item(atRow: outlineView.row(for: self)) else { return outlineView.draggingUpdated(sender) }
            if outlineView.isExpandable(representedItem) && !outlineView.isItemExpanded(representedItem) {
                return outlineView.draggingUpdated(sender)
            } else {
                return []
            }
        }
    
        //Actually spring-load!
        func springLoadingActivated(_ activated: Bool, draggingInfo: NSDraggingInfo) {
            //Whatever you want
        }
    
        func springLoadingHighlightChanged(_ draggingInfo: NSDraggingInfo) {
    
        }
    
        func springLoadingEntered(_ draggingInfo: NSDraggingInfo) -> NSSpringLoadingOptions {
            return [.enabled]
        }
    
        func springLoadingUpdated(_ draggingInfo: NSDraggingInfo) -> NSSpringLoadingOptions {
            return [.enabled]
        }
    }