拖放类似于 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 []
}
}
我想实现与 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 []
}
}