(Swift) Strange feedback when drag&drop NSOutlineview - swift

First of all, sorry for my English, I'll try my best to make it clear.(Edited with #Chip Jarred's suggestion,I've made some changes to simplify my question)
What I managed to do is achieving NSOutlineview drag & drop method, using .gap style(I just want to use this style!):
outlineView.draggingDestinationFeedbackStyle = .gap
And problems occurred, it could be easier described by a gif:
https://i.stack.imgur.com/0avhJ.gif
You can see drag&drop can run partly correctly. But the problem is: when I drag a node to the bottom of the list, a bit more below Node4, it will be dragged to the top of the list.
I've tried to fix it so I inserted a "print" fuction in validatDrop{}:
func outlineView(_ outlineView: NSOutlineView,
validateDrop info: NSDraggingInfo,
proposedItem item: Any?,
proposedChildIndex index: Int) -> NSDragOperation {
print("item:\(item),index:\(index)")
if index < 0 && item == nil{
return []
}else{
outlineView.draggingDestinationFeedbackStyle = .gap
return .move
}
}
The terminal told me that when I dropped a node to the top of the list or the bottom of the list, it returned a same index:
item:nil,index:0
https://i.stack.imgur.com/7pI7s.gif
And if I delete the .gap style:
func outlineView(_ outlineView: NSOutlineView,
validateDrop info: NSDraggingInfo,
proposedItem item: Any?,
proposedChildIndex index: Int) -> NSDragOperation {
print("item:\(item),index:\(index)")
if index < 0 && item == nil{
return []
}else{
// outlineView.draggingDestinationFeedbackStyle = .gap //ignore this line
return .move
}
}
https://i.stack.imgur.com/a8MMe.gif
Everything became normal. So it could be deduced that it could be not my "move" method's problem.
Sorry for English again, I would be grateful for any help.
And here is the essential part of my code:
outlineView.registerForDraggedTypes([.string])
...
func outlineView(_ outlineView: NSOutlineView,
heightOfRowByItem item: Any) -> CGFloat {
return 15
}
...
extension SceneCatalogView{
func outlineView(_ outlineView: NSOutlineView,
pasteboardWriterForItem item: Any) -> NSPasteboardWriting? {
let sourceNode = outlineView.row(forItem: item)
return "\(sourceNode)" as NSString
}
func outlineView(_ outlineView: NSOutlineView,
validateDrop info: NSDraggingInfo,
proposedItem item: Any?,
proposedChildIndex index: Int) -> NSDragOperation {
if index < 0 && item == nil{
return []
}else{
outlineView.draggingDestinationFeedbackStyle = .gap
return .move
}
}
func outlineView(_ outlineView: NSOutlineView,
acceptDrop info: NSDraggingInfo,
item: Any?,
childIndex index: Int) -> Bool {
let pasteboard = info.draggingPasteboard
let sourceNode = Int(pasteboard.string(forType: .string)!)!
let source = outlineView.item(atRow: sourceNode) as? Catalog
let content = source?.content
let targetNode = outlineView.row(forItem: item)
moveNode(sourceNode, targetNode, index) // Not finished
outlineView.reloadData() // Not finished
return true
}
}

After doing some research, I found multiple reports of a bug with NSTableView when using the .gap dragging style. It would seem that NSOutlineView is inheriting that bug. In any case, I found a work-around.
The problem is that when you drag below the last top-level item, the item and childIndex passed to outlineView(_:acceptDrop:item:childIndex) are always nil and 0, which are exactly the same values you get when dragging to the top of list. The only way I could find to differentiate between the two cases was to use the draggingLocation from NSDraggingInfo to compare against the first item's cell frame, and use that to translate the index.
func translateIndexForGapBug(
_ outlineView: NSOutlineView,
item: Any?,
index: Int,
for info: NSDraggingInfo) -> Int
{
guard outlineView.draggingDestinationFeedbackStyle == .gap,
items.count > 0,
item == nil,
index == 0
else { return index }
let point = outlineView.convert(info.draggingLocation, from: nil)
let firstCellFrame = outlineView.frameOfCell(atColumn: 0, row: 0)
return outlineView.isFlipped
? (point.y < firstCellFrame.maxY ? index : items.count)
: (point.y >= firstCellFrame.minY ? index : items.count)
}
I call it in outlineView(_:acceptDrop:item:childIndex):
func outlineView(
_ outlineView: NSOutlineView,
acceptDrop info: NSDraggingInfo,
item: Any?,
childIndex index: Int) -> Bool
{
assert(item == nil || item is Item)
trace("item = \(String(describing: item)), index = \(index)")
guard let sourceTitle = info.draggingPasteboard.string(forType: .string),
let source = parentAndChildIndex(forItemTitled: sourceTitle)
else { return false }
let debuggedIndex = translateIndexForGapBug(
outlineView,
item: item,
index: index,
for: info
)
moveItem(from: source, to: (item as? Item, debuggedIndex))
outlineView.reloadData()
return true
}
Since other drag styles seem to work, I only do this if it's set to .gap, so for the sake of testing, my outlineView(_:validateDrop:proposedItem:proposedChildIndex:) looks like this:
func outlineView(
_ outlineView: NSOutlineView,
validateDrop info: NSDraggingInfo,
proposedItem item: Any?,
proposedChildIndex index: Int) -> NSDragOperation
{
trace("item = \(String(describing: item)), index = \(index)")
guard info.draggingSource as? NSOutlineView === outlineView else {
return []
}
outlineView.draggingDestinationFeedbackStyle = .gap
if item == nil, index < 0 {
return []
}
return .move
}
However instead of setting it to .gap every time, you could probably just set it once when you set the data source in your outline view.
My definition of Item should be equivalent to your Catalog.
//------------------------------
class Item: CustomStringConvertible
{
var description: String { title }
var title: String
var children: [Item] = []
//------------------------------
init(_ title: String) { self.title = title }
convenience init(_ id: Int) { self.init("Item \(id)") }
//------------------------------
func addChild() {
children.append(Item("\(title).\(children.count + 1)"))
}
//------------------------------
func parentAndChildIndex(forChildTitled title: String) -> (Item?, Int)?
{
for i in children.indices
{
let child = children[i]
if child.title == title { return (self, i) }
if let found = child.parentAndChildIndex(forChildTitled: title){
return found
}
}
return nil
}
}
Here's full implementation of my data source:
//------------------------------
#objc class OVDataSource: NSObject, NSOutlineViewDataSource
{
//------------------------------
// Just creating some items programmatically for testing
var items: [Item] =
{
trace()
let items = (1...4).map { Item($0) }
items[2].addChild()
items[2].addChild()
return items
}()
//------------------------------
func outlineView(
_ outlineView: NSOutlineView,
pasteboardWriterForItem item: Any) -> NSPasteboardWriting?
{
trace()
guard let item = item as? Item else { return nil }
return item.title as NSString
}
//------------------------------
func outlineView(
_ outlineView: NSOutlineView,
numberOfChildrenOfItem item: Any?) -> Int
{
trace()
if let item = item {
return (item as? Item)?.children.count ?? 0
}
return items.count
}
//------------------------------
func outlineView(
_ outlineView: NSOutlineView,
child index: Int,
ofItem item: Any?) -> Any
{
trace()
if let item = item as? Item {
return item.children[index]
}
return items[index]
}
//------------------------------
func outlineView(
_ outlineView: NSOutlineView,
isItemExpandable item: Any) -> Bool
{
trace()
if let item = item as? Item {
return item.children.count > 0
}
return false
}
//------------------------------
func outlineView(
_ outlineView: NSOutlineView,
validateDrop info: NSDraggingInfo,
proposedItem item: Any?,
proposedChildIndex index: Int) -> NSDragOperation
{
trace("item = \(String(describing: item)), index = \(index)")
guard info.draggingSource as? NSOutlineView === outlineView else {
return []
}
outlineView.draggingDestinationFeedbackStyle = .gap
if item == nil, index < 0 {
return []
}
return .move
}
//------------------------------
func outlineView(
_ outlineView: NSOutlineView,
acceptDrop info: NSDraggingInfo,
item: Any?,
childIndex index: Int) -> Bool
{
assert(item == nil || item is Item)
trace("item = \(String(describing: item)), index = \(index)")
guard let sourceTitle = info.draggingPasteboard.string(forType: .string),
let source = parentAndChildIndex(forItemTitled: sourceTitle)
else { return false }
let debuggedIndex = translateIndexForGapBug(
outlineView,
item: item,
index: index,
for: info
)
moveItem(from: source, to: (item as? Item, debuggedIndex))
outlineView.reloadData()
return true
}
//------------------------------
func translateIndexForGapBug(
_ outlineView: NSOutlineView,
item: Any?,
index: Int,
for info: NSDraggingInfo) -> Int
{
guard outlineView.draggingDestinationFeedbackStyle == .gap,
items.count > 0,
item == nil,
index == 0
else { return index }
let point = outlineView.convert(info.draggingLocation, from: nil)
let firstCellFrame = outlineView.frameOfCell(atColumn: 0, row: 0)
return outlineView.isFlipped
? (point.y < firstCellFrame.maxY ? index : items.count)
: (point.y >= firstCellFrame.minY ? index : items.count)
}
//------------------------------
func parentAndChildIndex(forItemTitled title: String) -> (parent: Item?, index: Int)?
{
trace("Finding parent and child for item: \"\(title)\"")
for i in items.indices
{
let item = items[i]
if item.title == title { return (nil, i) }
if let found = item.parentAndChildIndex(forChildTitled: title) {
return found
}
}
return nil
}
//------------------------------
func moveItem(
from src: (parent: Item?, index: Int),
to dst: (parent: Item?, index: Int))
{
trace("src = \(src), dst = \(dst)")
let item: Item = src.parent?.children[src.index]
?? items[src.index]
if src.parent === dst.parent // Moving item in same level?
{
if let commonParent = src.parent
{
moveItem(
item,
from: src.index,
to: dst.index,
in: &commonParent.children
)
return
}
moveItem(item, from: src.index, to: dst.index, in: &items)
return
}
// Moving between levels
if let srcParent = src.parent {
srcParent.children.remove(at: src.index)
}
else { items.remove(at: src.index) }
if let dstParent = dst.parent {
insertItem(item, into: &dstParent.children, at: dst.index)
}
else { insertItem(item, into: &items, at: dst.index) }
}
//------------------------------
// Move an item within the same level
func moveItem(
_ item: Item,
from srcIndex: Int,
to dstIndex: Int,
in items: inout [Item])
{
if srcIndex < dstIndex
{
insertItem(item, into: &items, at: dstIndex)
items.remove(at: srcIndex)
return
}
items.remove(at: srcIndex)
insertItem(item, into: &items, at: dstIndex)
}
func insertItem(_ item: Item, into items: inout [Item], at index: Int)
{
if index < 0
{
items.append(item)
return
}
items.insert(item, at: index)
}
}
The trace() calls are just for debugging. Either remove them, or implement it:
func trace(
_ message: #autoclosure () -> String = "",
function: StaticString = #function,
line: UInt = #line)
{
#if DEBUG
print("\(function):\(line): \(message())")
#endif
}

Related

Encountered an error in "Generic Where Clauses", still not able to figure out why! While it is same in the swift book

Topic: Generic Where Clauses
protocol Container {
associatedtype Item
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
}
struct Stack<Element>: Container {
var items = [Element]()
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
mutating func append(_ item: Element) {
self.push(item)
}
var count: Int {
return items.count
}
subscript(i: Int) -> Element {
return items[i]
}
}
func allItemsMatch<C1: Container, C2: Container>
(_ someContainer: C1, _ anotherContainer: C2) -> Bool
where C1.Item == C2.Item, C1.Item: Equatable {
if someContainer.count != anotherContainer.count {
return false
}
for i in 0..<someContainer.count {
if someContainer[i] != anotherContainer[i] {
return false
}
}
return true
}
var stackOfStrings = Stack<String>()
stackOfStrings.push("uno")
stackOfStrings.push("dos")
stackOfStrings.push("tres")
var arrayOfStrings = ["uno", "dos", "tres"]
Error comes here in if-else control flow
if allItemsMatch(stackOfStrings, arrayOfStrings) {
print("All items match.")
} else {
print("Not all items match.")
}
Error says that "arrayOfStrings" doesn't conform to the Container Protocol.
Tried many times but not able to figure out
Error exactly says - " Global function 'allItemsMatch' requires that '[String]' conform to 'Container' "
As already mentioned in comments you forgot to declare that Array conforms to the Container protocol.
extension Array: Container { }
Not related to your question but you should also make your Stack structure conform to Sequence as well. It will allow you to use Sequence method elementsEqual
func elementsEqual<OtherSequence>(_ other: OtherSequence) -> Bool where OtherSequence : Sequence, Self.Element == OtherSequence.Element
So just add an index property to your Stack type and implement Sequence next property as follow:
struct Stack<Element>: Container {
typealias Item = Element
var items = [Item]()
mutating func push(_ item: Item) { items.append(item) }
mutating func pop() -> Item { items.removeLast() }
mutating func append(_ item: Item) { push(item) }
var count: Int { items.count }
subscript(i: Int) -> Item { items[i] }
var index: Int = 0
}
extension Stack: Sequence, IteratorProtocol {
mutating func next() -> Item? {
guard index < items.endIndex else { return nil }
defer { index += 1}
return items[index]
}
}
Playground testing:
var stackOfStrings = Stack<String>()
stackOfStrings.push("uno")
stackOfStrings.push("dos")
stackOfStrings.push("tres")
var arrayOfStrings = ["uno", "dos", "tres"]
stackOfStrings.elementsEqual(arrayOfStrings) // true

Why View-Based NSOutlineView with autosaveExpandedItems true ignores expanded upon reloadData?

I use a NSOutlineView that auto saves expanded state. If I manually reload data when dataSource updates, the func outlineView(_ outlineView: NSOutlineView, itemForPersistentObject object: Any) -> Any? datasource method is not called anymore and every cell collapses. Any idea why this might happen?
Tried to reloadItem with nil send as param but still no good.
I use this for persisting expanded rows:
func outlineView(_ outlineView: NSOutlineView, persistentObjectForItem item: Any?) -> Any? {
return NSKeyedArchiver.archivedData(withRootObject: item)
}
func outlineView(_ outlineView: NSOutlineView, itemForPersistentObject object: Any) -> Any? {
guard let data = object as? Data,
let item = NSKeyedUnarchiver.unarchiveObject(with: data) as? Category else { return nil }
let foundItem = recursiveSearch(for: item, in: viewModel.dataSource.value)
return foundItem
}
And this to reloadData:
viewModel.dataSource.subscribe(onNext: { [weak self] _ in
self?.outlineView.reloadData()
}).disposed(by: disposeBag)
IMHO autosaving is sort of half-baked feature and it doesn't work as expected. In other words, it's implemented in a way that it restores the state when your application launches (just once) and then you're on your own.
Implement your own one utilizing outlineViewItemDidExpand(_:) & outlineViewItemDidCollapse(_:) (especially when we're reloading, ...).
Couple of tricks you can use if you do not want to implement custom autosaving. But I wouldn't rely on them.
First trick - tell the NSOutlineView to reload persistent state
NSOutlineView inherits from the NSTableView and the autosaveName property documentation says:
If you change the value of this property to a new name, the table reads in any saved information and sets the order and width of this table view’s columns to match. Setting the name to nil removes any previously stored state from the user defaults.
What is inaccurate here - setting it to nil doesn't remove previously stored expanded items state for NSOutlineView. We can use it to force the NSOutlineView to reload expanded items state:
class ViewController: NSViewController, NSOutlineViewDelegate, NSOutlineViewDataSource {
#IBOutlet var outlineView: NSOutlineView!
// It's for testing, to demonstrate the persistent state reloading
private var doNotLoad = true
override func viewDidAppear() {
super.viewDidAppear()
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
self.doNotLoad = false
let autosaveName = self.outlineView.autosaveName
self.outlineView.autosaveName = nil
self.outlineView.reloadData()
self.outlineView.autosaveName = autosaveName
}
}
func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
if (doNotLoad) {
return 0
}
return item == nil ? data.count : (item as! Node).children.count
}
}
If you'd like to comply with the documentation, do not use nil and set some fake name. But I would expect that once the bug is fixed, the persistent state will be removed if we change the autosaveName or if we set it set to nil.
Second trick - load & expand yourself
Imagine you have the following Node class:
class Node {
let id: Int
let children: [Node]
// ...
}
And your data source implements:
func outlineView(_ outlineView: NSOutlineView, persistentObjectForItem item: Any?) -> Any? {
(item as! Node).id
}
func outlineView(_ outlineView: NSOutlineView, itemForPersistentObject object: Any) -> Any? {
guard let id = object as? Int else { return nil }
return data.firstNode { $0.id == id }
}
The firstNode is not related to this question, but here's the implementation (because it's mentioned in the code):
extension Array where Self.Element == Node {
// Search for a node (recursively) until a matching element is found
func firstNode(where predicate: (Element) throws -> Bool) rethrows -> Element? {
for element in self {
if try predicate(element) {
return element
}
if let matched = try element.children.firstNode(where: predicate) {
return matched
}
}
return nil
}
}
Then you can reloadData & expand all the items by yourself:
outlineView.reloadData()
if outlineView.autosaveExpandedItems,
let autosaveName = outlineView.autosaveName,
let persistentObjects = UserDefaults.standard.array(forKey: "NSOutlineView Items \(autosaveName)"),
let itemIds = persistentObjects as? [Int] {
itemIds.forEach {
let item = outlineView.dataSource?.outlineView?(self.outlineView, itemForPersistentObject: $0)
self.outlineView.expandItem(item)
}
}

NSOutlineView Never Reaching Datasource Children

Swift 4, Xcode 9.1
I can only get the first level of items to render in my NSOutlineView. The child items never work. Below is my code with comments that show what gets logged to the console.
class SidebarSection{
var title:String!
var buttonHidden:Bool!
}
class SidebarItem{
var title:String!
}
loadStuff(){
print(sections) //[MyApp.SidebarSection, MyApp.SidebarSection]
print(items) //[[MyApp.SidebarItem, MyApp.SidebarItem, MyApp.SidebarItem], [MyApp.SidebarItem]]
sidebarOutlineView.reloadData()
}
//Over in my NSOutlineViewDelegate/Datasource...
func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
print(item) //nil
print(sections) //[MyApp.SidebarSection, MyApp.SidebarSection]
if let section = item as? SidebarSection{
print("This never gets logged.")
//...
}else{
//Sections
print("Returned: \(sections.count)") //2
return sections.count
}
}
func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
print("\(index) / \(item)") //First time: 0 / nil; Second time: 1 / nil
if let section = item as? SidebarSection{
print("This never gets logged, either.")
//...
}else{
//Items
return sections[index]
}
}
I tried if let section = item as? SidebarSection{} right after my data gets added to the sections array, and it detects the type just fine. I can't figure out what I'm doing wrong.
Any ideas?

swift outline view with two sub children

i working with swift 4 for macOS and i have a NSOutlineView:
i get the data from core data.
structure:
entity Person (relationship to entity Book)
entity Book
My Code for this result:
#IBOutlet weak var myOutlineView: NSOutlineView!
let context = (NSApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
var people = [Person]()
override func viewWillAppear() {
requestPeople()
}
func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
let view = outlineView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "Cell"), owner: self) as? CustomCell
if let person = item as? Person {
// Show Person
} else if let book = item as? Book {
// Show Books
}
return view
}
func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
if let person = item as? Person {
return person.books.count
}
return people.count
}
func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
if let person = item as? Person {
return person.books[index]
}
return people[index]
}
func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
if let person = item as? Person {
return person.books.count > 0
}
return false
}
func requestPeople() {
let request = NSFetchRequest<Person>(entityName: "Person")
do {
people = try context.fetch(request)
myOutlineView.reloadData()
} catch { print(error) }
}
now my problem:
i would like create another outline view.
My Book entity looks like this (attributes):
name
creationDate
My new outlineview should get this structure:
+ Year
++ Month
+++ Bookname
but i dont know how can I realize this structure.
It is different as my first outline view.
can somebody help me?
=======
i guess that i have create arrays for year and month without duplicates.
for this i try a this function to get the data:
var year = [String]()
var month = [String]()
var books = [Book]()
func requestBooks() {
let request = NSFetchRequest<Book>(entityName: "Book")
do {
books = try context.fetch(request)
for x in 0 ...< books.count {
if !year.contains("\(Calendar.current.component(.year, from: books[x].creationDate))") {
year.append("\(Calendar.current.component(.year, from: books[x].creationDate))")
}
if !month.contains("\(Calendar.current.component(.month, from: books[x].creationDate))") {
month.append("\(Calendar.current.component(.month, from: books[x].creationDate))")
}
}
myOutlineView.reloadData()
} catch { print(error) }
}
A multi-level outline is easier to manage when your underlying data structure is hierarchical (i.e. a tree structure).
Here's an example of how you can create a "Tree" node class for your Books:
class BookNode
{
// levels and relationships, from parent to children
enum Level { case Top, Year, Month, Book }
let subLevels:[Level:Level] = [ .Top:.Year, .Year:.Month, .Month:.Book ]
var label = "" // description and unique "key"
var level = Level.Top
var children : [BookNode] = []
var book : Book! = nil // .Book level will store the actual Book
// add book to hierarchy, auto-create intermediate levels
func add(_ book:Book)
{
var subLabel = ""
switch level
{
case .Top : subLabel = String(Calendar.current.component(.year, from:book.creationDate))
case .Year : subLabel = String(Calendar.current.component(.month, from:book.creationDate))
case .Month : subLabel = book.name
case .Book : self.book = book // last level stores the book
return // and has no children
}
// Add branch (.Year, .Month) or leaf (.Book) node as needed
var subNode:BookNode! = children.first{$0.label == subLabel}
if subNode == nil
{
subNode = BookNode()
subNode.level = subLevels[level]!
subNode.label = subLabel
children.append(subNode)
}
// keep adding recursively down to .Book level
subNode.add(book)
}
}
Your data will be stored in a hierarchy of BookNodes which you can load from your fetch request
(you can pre-sort it, as I did, or leave that up to the BookNode class)
var topNode = BookNode()
func requestBooks()
{
let request = NSFetchRequest<Book>(entityName: "Book")
do {
let books = try context.fetch(request)
topNode = BookNode()
for book in books.sorted(by:{$0.creationDate < $1.creationDate})
{
topNode.add(book)
}
}
}
With this, it will be easy to respond to your outline protocols using the BookNodes as the outline items:
func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView?
{
let view = outlineView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "Cell"), owner: self) as? CustomCell
let node = (item as? BookNode) ?? topNode
switch node.level
{
case .Year : // show year : node.label
case .Month : // show month : node.label
case .Book : // show book name : node.label and/or node.book
default : break
}
return view
}
func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int
{
let node = (item as? BookNode) ?? topNode
return node.children.count
}
func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any
{
let node = (item as? BookNode) ?? topNode
return node.children[index]
}
func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool
{
let node = (item as? BookNode) ?? topNode
return node.children.count > 0
}
If you program needs to allow adding/changing/removing individual books, the BookNode class can be used to reflect the individual changed (e.g. remove a book child or add a new one). You will then only need to call reloadData() on the outline without having to get everything back from the database.

Finder URL interpretation

In this routine - tableView validateDrop, I need to understand the items returned. The return items array appears to be file ids?
func tableView(tableView: NSTableView, validateDrop info: NSDraggingInfo,
proposedRow row: Int, proposedDropOperation dropOperation: NSTableViewDropOperation) -> NSDragOperation {
print("validate drop \(dropOperation)")
if dropOperation == .Above {
//get the file URLs from the pasteboard
let pasteboard = info.draggingPasteboard()
//list the file type UTIs we want to accept
let options = [NSPasteboardURLReadingFileURLsOnlyKey : true,
NSPasteboardURLReadingContentsConformToTypesKey : [kUTTypeMovie as String]]
let items = pasteboard.readObjectsForClasses([NSURL.classForCoder()],
options: options)
if items!.count > 0 {
for item in items! {
print("item -> \(item)")
}
print("validate Above -> .Copy")
return .Copy;
} else {
print("validate Above -> .Move")
return .Move
}
}
print("validate other -> .None")
return .None
}
which outputs:
item -> file:///.file/id=6571367.34508463
At some point I need to translate this to what I can use - a typical file:// formatted URL.
duh
po item.filePathURL
▿ Optional<NSURL>
- Some : file:///Users/slashlos/Movies/Flight%20to%20Mars.m4v
which I'd probably unescape for user viewing