拖放类似于 Finder 应用程序 NSOutlineView Cocoa Swift

Drag and Drop similar to Finder application NSOutlineView Cocoa Swift

我想实现与 NSOutlineView 类似的拖放 Mac Finder 应用程序。在我当前的实现中,拖放会话验证下降到每个 parent 的 children。我不想要那个。我只想将 child 从一个 parent 删除到另一个 parent。就像在 Finder 中将文件从一个文件夹移动到另一个文件夹一样。怎么做?下面是包含我的拖放代码的示例代码。

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"),
            ])
        ]
    }
}

上述方法的当前结果:

预期结果:

给你:

func outlineView(_ outlineView: NSOutlineView, validateDrop info: NSDraggingInfo,
        proposedItem item: Any?, proposedChildIndex index: Int) -> NSDragOperation {

    // start at the proposed item
    var destinationItem = item as? NSTreeNode

    // if the drop is between two rows then find the row under the cursor
    if index != NSOutlineViewDropOnItemIndex {
        if let mouseLocation = NSApp.currentEvent?.locationInWindow {
            let point = outlineView.convert(mouseLocation, from: nil)
            let row = outlineView.row(at: point)
            if row >= 0 {
                destinationItem = outlineView.item(atRow: row) as? NSTreeNode
            }
            else {
                destinationItem = nil
            }
        }
    }

    // if the drop is on a leaf then the destination is the parent item
    if destinationItem?.isLeaf ?? false {
        destinationItem = destinationItem?.parent
    }

    // change the drop item
    if destinationItem != nil {
        outlineView.setDropItem(destinationItem, dropChildIndex: NSOutlineViewDropOnItemIndex)
        return .move
    }
    else {
        return []
    }
}