I want to implement drag and drop with NSOutlineView similar Mac Finder application. With my current implementation, drag and drop session validates drop to children of each parent. I don't want that. I only want to drop child from one parent to another parent. like moving file from one folder to another folder in Finder. How to do that? below is a sample code with my drag and drop code included.
import Cocoa
class ViewController: NSViewController {
@IBOutlet weak var outlineView: NSOutlineView!
private let treeController = NSTreeController()
@objc dynamic var content = [Node]()
override func viewDidLoad() {
super.viewDidLoad()
outlineView.delegate = self
outlineView.dataSource = self
treeController.objectClass = Node.self
treeController.childrenKeyPath = "children"
treeController.countKeyPath = "count"
treeController.leafKeyPath = "isLeaf"
outlineView.gridStyleMask = .solidHorizontalGridLineMask
outlineView.autosaveExpandedItems = true
treeController.bind(NSBindingName(rawValue: "contentArray"),
to: self,
withKeyPath: "content",
options: nil)
outlineView.bind(NSBindingName(rawValue: "content"),
to: treeController,
withKeyPath: "arrangedObjects",
options: nil)
content.append(contentsOf: NodeFactory().nodes())
outlineView.registerForDraggedTypes([.string])
outlineView.target = self
}
}
extension ViewController: NSOutlineViewDelegate, NSOutlineViewDataSource {
public func outlineView(_ outlineView: NSOutlineView,
viewFor tableColumn: NSTableColumn?,
item: Any) -> NSView? {
var cellView: NSTableCellView?
guard let identifier = tableColumn?.identifier else { return cellView }
switch identifier {
case .init("node"):
if let view = outlineView.makeView(withIdentifier: identifier,
owner: outlineView.delegate) as? NSTableCellView {
view.textField?.bind(.value,
to: view,
withKeyPath: "objectValue.value",
options: nil)
cellView = view
}
case .init("count"):
if let view = outlineView.makeView(withIdentifier: identifier,
owner: outlineView.delegate) as? NSTableCellView {
view.textField?.bind(.value,
to: view,
withKeyPath: "objectValue.childrenCount",
options: nil)
cellView = view
}
default:
return cellView
}
return cellView
}
func outlineView(_ outlineView: NSOutlineView, pasteboardWriterForItem item: Any) -> NSPasteboardWriting? {
let row = self.outlineView.row(forItem: item)
let pasteboardItem = NSPasteboardItem.init()
pasteboardItem.setString("\(row)", forType: .string)
return pasteboardItem
}
func outlineView(_ outlineView: NSOutlineView, validateDrop info: NSDraggingInfo, proposedItem item: Any?, proposedChildIndex index: Int) -> NSDragOperation {
if let node = (item as? NSTreeNode)?.representedObject as? Node {
if node.count >= 0 {
return .move
}
}
return .init(rawValue: 0)
}
}
@objc public class Node: NSObject {
@objc let value: String
@objc var children: [Node]
@objc var childrenCount: String? {
let count = children.count
guard count > 0 else { return nil }
return "\(count) node\(count > 1 ? "s" : "")"
}
@objc var count: Int {
children.count
}
@objc var isLeaf: Bool {
children.isEmpty
}
init(value: String, children: [Node] = []) {
self.value = value
self.children = children
}
}
final class NodeFactory {
func nodes() -> [Node] {
return [
.init(value: "???? Offers", children: [
.init(value: "???? Ice Cream"),
.init(value: "☕️ Coffee"),
.init(value: "???? Burger")
]),
.init(value: "Retailers", children: [
.init(value: "King Soopers"),
.init(value: "Walmart"),
.init(value: "Target"),
])
]
}
}
Current result with above approach:
Expected result:
outlineView(_:validateDrop:proposedItem:proposedChildIndex:)
? – WillekecollectionView(_:validateDrop:proposedIndexPath:dropOperation:)
instead ofcollectionView(_:validateDrop:proposedIndex:dropOperation:)
and take a look at the documentation. – WillekeNSCollectionsView
andNSOutlineVIew
again. I'm sorry. – Willeke