Drag from NSOutlineView to SwiftUI view?

I'm developing using Xcode 12.3 (12C33) on macOS 11.1 (20C69).

My application has a sidebar drawn using NSOutlineView (kind of vaguely a source list) and a main pane also drawn using NSOutlineView. The main pane's NSOutlineView's fields are drawn using SwiftUI. I'm trying to drag a data element from the sidebar into a field in the main pane's outline view.

In the sidebar NSOutlineViewDataSource, I have this function:
Code Block
func outlineView(
_ outlineView: NSOutlineView,
pasteboardWriterForItem item: Any)
-> NSPasteboardWriting?
{
sidebarControllerLogger.trace("Drag operation started.")
guard let workingItem = item as? MyEntity
else {
sidebarControllerLogger.debug("Dragging something which isn't an object! Description: \(String(describing: item), privacy: .public)")
return nil
}
return workingItem.name! as NSString
}

As you might guess, "MyEntity" is a Core Data entity. I don't think that should affect this, but I mention it just in case. The drag works. I can drag out of the application into TextEdit, and I get the object's name.

My drop target is, as mentioned earlier, a SwiftUI view:
Code Block
VStack{
ForEach(fieldArray, id:\.self) { item in
Text(item.name!)
}
}
.padding(3)
.onDrop(of: ["public.text"], isTargeted: nil) { providers in
mainViewLogger.trace("Entered drop handler block.")
for provider in providers {
print("Provider found. It provides types \(String(describing: provider.registeredTypeIdentifiers))")
provider.loadItem(forTypeIdentifier: "public.text", options: nil) { item, error in
print("item: \(String(describing: item))")
print("error: \(String(describing: error))")
}
}
return false
}

The of: seems to work. The drag gets the little green + orb when over that field and it goes away when over other fields. The drop is giving me NSItemProvider objects for each dragged item. The problem is the NSItemProvider objects don't seem to have any registered types. Here's what I get when I drag a single item from the sidebar to the field which accepts drops:
Code Block
2021-01-21 21:52:21.680925-0600 MyApp[8505:306504] [Sidebar Controller] Drag operation started.
2021-01-21 21:52:23.334928-0600 MyApp[8505:306504] [Main View] Entered drop handler block.
Provider found. It provides types []
item: nil
error: Optional(Error Domain=NSItemProviderErrorDomain Code=-1000 "Cannot load representation of type public.text" UserInfo={NSLocalizedDescription=Cannot load representation of type public.text})

I have tried this with a full delegate implementation where I ask the DropInfo for all the NSItemProviders with "public.text" representations. I get a list of providers. When I ask them for their "public.text", I get the same "Cannot load representation of type public.text".

• I could understand maybe dragging an NSString to the pasteboard doesn't spit out a "public.text" object. That's easy enough to handle. If I'm understanding the object properties properly, though, it doesn't have any representations. How does that happen?

• Why is my drop target lighting up if none of the NSItemProviders I get can provide the single type I say the drop target accepts? If I change it to some other type, the drop target no longer lights up, and no longer accepts the drop.

• Does anybody have any suggestions on what to try next?

Accepted Reply

Further update. I tried simply ignoring the NSItemProvider objects and instead finding the drag pasteboard and reading the objects from it directly. This feels really awkward, but it works. My drag source is the same as my original post. Here is the working drop target:
Code Block Swift
.onDrop(of: ["public.text"], isTargeted: nil) { _ in
mainViewLogger.trace("Entered drop handler block.")
let dragBoard = NSPasteboard(name: .drag)
guard let draggedItems = dragBoard.readObjects(forClasses: [NSString.self], options: nil) else { return false }
print("Pulled \(draggedItems.count) items from the drag pasteboard: \(String(describing: draggedItems))")
return false
}

This code obviously doesn't do anything with the dropped data, and it always reports an unsuccessful drop, but the draggedItems list does actually contain the dragged objects.

Replies

I realized this morning I had not tried dragging from TextEdit into the SwiftUI view. Again, the green orb with the + shows up, and again when I drop the text, I get a provider with no registered types.

If I change the NSOutlineDataSource method to this:

Code Block Swift
func outlineView(
_ outlineView: NSOutlineView,
pasteboardWriterForItem item: Any)
-> NSPasteboardWriting?
{
sidebarControllerLogger.trace("Drag operation started.")
guard let workingItem = item as? MyEntity
else {
sidebarControllerLogger.debug("Dragging something which isn't an object! Description: \(String(describing: item), privacy: .public)")
return nil
}
let provider = NSItemProvider(object: workingItem.name! as NSString)
print("Provider created for types \(String(describing:provider.registeredTypeIdentifiers))")
return workingItem.name! as NSString
}

then I get this in my logs when I start a drag:
Code Block none
Provider created for types ["public.utf8-plain-text"]

This seems to me to reinforce the idea that "public.text" is an appropriate UTType for my test drop target code. The provider still doesn't offer it, though. If I try to return the provider I made above, I get a build-time error: "Type of expression is ambiguous without more context".
Further update. I tried simply ignoring the NSItemProvider objects and instead finding the drag pasteboard and reading the objects from it directly. This feels really awkward, but it works. My drag source is the same as my original post. Here is the working drop target:
Code Block Swift
.onDrop(of: ["public.text"], isTargeted: nil) { _ in
mainViewLogger.trace("Entered drop handler block.")
let dragBoard = NSPasteboard(name: .drag)
guard let draggedItems = dragBoard.readObjects(forClasses: [NSString.self], options: nil) else { return false }
print("Pulled \(draggedItems.count) items from the drag pasteboard: \(String(describing: draggedItems))")
return false
}

This code obviously doesn't do anything with the dropped data, and it always reports an unsuccessful drop, but the draggedItems list does actually contain the dragged objects.