I\'d like to reproduce the Xcode blue drag line in my app.
Do you know a way to code this ?
I know how to draw a line using Core Graphics ... But th
Trying to adopt Rob Mayoff's excellent solution above into my own project's interface, which is based around an NSOutlineView, I ran into a few problems. In case it helps anyone trying to achieve the same thing, I'll detail those pitfalls in this answer.
The sample code provided in the solution detects the start of a drag by implementing mouseDown(with:) on the view controller, and then calling hittest() on the window's content view in order to obtain the DragEndpoint subview where the (potential) drag is originating. When using outline views, this causes two pitfalls detailed in the next sections.
It seems that when a table view or outline view is involved, mouseDown(with:) never gets called on the view controller, and we need to instead override that method in the outline view itself.
NSTableView -and by extension, NSOutlineView- overrides the NSResponder method validateProposedFirstResponder(_:for:), and this causes the hittest() method to fail: it always returns the outline view itself, and all subviews (including our target DragEndpoint subview inside the cell) remain inaccessible.
From the documentation:
Views or controls in a table sometimes need to respond to incoming events. To determine whether a particular subview should receive the current mouse event, a table view calls
validateProposedFirstResponder:forEvent:in its implementation ofhitTest. If you create a table view subclass, you can overridevalidateProposedFirstResponder:forEvent:to specify which views can become the first responder. In this way, you receive mouse events.
At first I tried overriding:
override func validateProposedFirstResponder(_ responder: NSResponder, for event: NSEvent?) -> Bool {
if responder is DragEndpoint {
return true
}
return super.validateProposedFirstResponder(responder, for: event)
}
...and it worked, but reading the documentation further suggests a smarter, less intrusive approach:
The default
NSTableViewimplementation ofvalidateProposedFirstResponder:forEvent:uses the following logic:
Return
YESfor all proposed first responder views unless they are instances or subclasses ofNSControl.Determine whether the proposed first responder is an
NSControlinstance or subclass. If the control is anNSButtonobject, returnYES. If the control is not anNSButton, call the control’shitTestForEvent:inRect:ofView:to see whether the hit area is trackable (that is,NSCellHitTrackableArea) or is an editable text area (that is,NSCellHitEditableTextArea), and return the appropriate value. Note that if a text area is hit,NSTableViewalso delays the first responder action.
(emphasis mine)
...which is weird, because it feels like it should say:
- Return
NOfor all proposed first responder views unless they are instances or subclasses ofNSControl.
, but anyway, I instead modified Rob's code to make DragEndpoint a subclass of NSControl (not just NSView), and that works too.
Because NSOutlineView only exposes a limited number of drag-and-drop events through its data source protocol (and the drag session itself can not be meaningfully modified from the data source's side), it seems that taking full control of the drag session is not possible unless we subclass the outline view and override the NSDraggingSource methods.
Only by overriding draggingSession(_:willBeginAt:) at the outline view itself can we prevent calling the superclass implementation and starting an actual item drag (which displays the dragged row image).
We could start a separate drag session from the mouseDown(with:) method of the DragEndpoint subview: when implemented, it is called before the same method on the outline view (which in turn is what triggers the dragging session to be started). But if we move the dragging session away from the outline view, it seems like it will be impossible to have springloading "for free" when dragging above an expandable item.
So instead, I discarded the ConnectionDragController class and moved all its logic to the outline view subclass: the tackDrag() method, the active DragEndpoint property, and all methods of the NSDraggingSource protocol into the outline view.
Ideally, I would have liked to avoid subclassing NSOutlineView (it is discouraged) and instead implement this behaviour more cleanly, exclusively through the outline view's delegate/data source and/or external classes (like the original ConnectionDragController), but it seems that it is impossible.
I haven't got the springloading part to work yet (it was working at a moment, but not now so I'm still looking into it...).
I too made a sample project, but I'm still fixing minor issues. I'll post a link to the GiHub repository as soon as it is ready.