Как написать в NSPasteboard и получить его тем же указателем

У меня есть собственный класс, который соответствует протоколу NSCoding. Ниже показано, как я реализую методы протокола.

User.swift

required init?(coder aDecoder: NSCoder) {
    self.name = aDecoder.decodeObject(forKey: #keyPath(name)) as! String
    self.age = aDecoder.decodeInteger(forKey: #keyPath(age))
}

func encode(with aCoder: NSCoder) {
    aCoder.encode(name, forKey: #keyPath(name))
    aCoder.encode(age, forKey: #keyPath(age))
}

Я установил параметры чтения:

static func readingOptions(forType type: String, pasteboard: NSPasteboard) -> NSPasteboardReadingOptions {
    return .asKeyedArchive
}

Этот объект будет помещаться внутрь NSDragPboard всякий раз, когда пользователь перетаскивает строку ячейки на NSOutlineView. Вы можете увидеть реализацию ниже:

UserViewController.h

var users: User // Store the users to be used in the outlineView

.
.
.

func outlineView(_ outlineView: NSOutlineView, writeItems items: [Any], to pasteboard: NSPasteboard) -> Bool {
    if let user = items.first as? User {
        pasteboard.clearContents()
        pasteboard.writeObjects([user])
        return true
    }
    return false
}

После того, как пользователь закончит перетаскивание, отпустив кнопку мыши, приложение получит содержимое монтажного стола, выполнив следующие действия:

func outlineView(_ outlineView: NSOutlineView, acceptDrop info: NSDraggingInfo, item: Any?, childIndex index: Int) -> Bool {

    let pasteboard = info.draggingPasteboard()

    if let userInPasteboard = pasteboard.readObjects(forClasses: [User.self], options: nil)?.first as? User {
        // We can access the user in pasteboard here
    }
}

Но проблема в том, что данные из картона имеют другой адрес памяти, чем данные в layoutView.

Если мы попытаемся найти пользователя в layoutView, используя пользователя из pasteBoard, мы не сможем его найти.

for user in self.users {
    if user == userInPasteboard {
        print("We found the user that was placed on pasteboard") // Never executed
    }
}

Я могу реализовать протокол Equatable для класса User. Но я думаю, что если каким-то образом мы сможем сделать так, чтобы объект, считываемый с монтажного стола, имел тот же адрес указателя, что и объект, записанный на монтажный стол, это сработало бы.

Ниже приведено то, чего я пытаюсь достичь:

NSPasteboard иллюстрация письма и чтения

Можно ли этого достичь? Как?


person Edward Anthony    schedule 26.07.2017    source источник
comment
Нет, ты не можешь этого сделать. Является ли перетаскивание внутри одного и того же контурного вида?   -  person Willeke    schedule 26.07.2017
comment
@Willeke Да, это внутри того же контура. Только для переоформления.   -  person Edward Anthony    schedule 26.07.2017
comment
Поместите пути индекса на монтажный стол.   -  person Willeke    schedule 27.07.2017
comment
Обратите внимание, что если вы помещаете индексные пути на монтажный стол и у вас есть несколько структурных представлений в вашем приложении, все из которых поддерживают перетаскивание, то (при принятии перетаскивания) вам все равно нужно смотреть на источник перетаскивания, и вам нужно сопоставить индексировать пути обратно к объектам. Это не обязательно проще или лучше, чем просто переход непосредственно к источнику перетаскивания для перетаскиваемых объектов, и, безусловно, менее эффективно.   -  person rob mayoff    schedule 27.07.2017
comment
@EdwardAnthony Почему ты хочешь сделать user == userInPasteboard? Вы хотите изменить порядок внутри того же вида схемы?   -  person Willeke    schedule 27.07.2017
comment
@robmayoff сопоставление индексных путей с объектами — это то, что источник данных делает все время.   -  person Willeke    schedule 27.07.2017
comment
Я только что просмотрел свои проекты, в которых используются представления структуры, и ни один из них по какой-либо причине не сопоставляет пути индекса с элементами представления структуры. Я уверен, что NSOutlineView делает это внутри, но я никогда не находил это необходимым в своих источниках данных. Кроме того, если ваше приложение может добавлять элементы в представление схемы независимо от пользовательского ввода (как некоторые из моих делают на основе сетевых событий), то пути индекса нестабильны и, следовательно, не могут надежно идентифицировать перетаскиваемые элементы.   -  person rob mayoff    schedule 27.07.2017
comment
Вы не можете сделать это, используя только индексные пути, потому что вы можете добавлять поддерево столько, сколько хотите. Ближайшее решение - передать full path как /Projects/App/hello-world. Если у меня есть представление структуры с глубиной 5, и я хочу переместить элемент между деревьями, я никак не могу сохранить ссылку на этот элемент, используя только пути индекса. По этой причине я думаю, что передача индексных путей в NSPasteboard не является хорошим решением.   -  person Edward Anthony    schedule 27.07.2017


Ответы (1)


Есть несколько способов справиться с этим. Поскольку вы сказали (в комментарии), что это только для изменения порядка в одном представлении схемы, я объясню, как я это сделал.

Во-первых, я добавил в свой источник данных приватную переменную для отслеживания перетаскиваемых элементов:

private var itemsBeingDragged = [MyItem]()

Поскольку я буду использовать это во время перетаскивания, чтобы получить перетаскиваемые элементы, на самом деле не имеет значения, что находится на монтажном столе. Я реализовал outlineView(_:pasteboardWriterForItem:) так:

func outlineView(_ outlineView: NSOutlineView, pasteboardWriterForItem item: Any) -> NSPasteboardWriting? {
    guard let node = item as? MyNode else { return nil }
    let pasteboardItem = NSPasteboardItem()
    pasteboardItem.setString(String(describing: node), forType: dragType)
    return pasteboardItem
}

Чтобы установить и очистить itemsBeingDragged, я реализовал следующие методы:

func outlineView(_ outlineView: NSOutlineView, draggingSession session: NSDraggingSession, willBeginAt screenPoint: NSPoint, forItems draggedItems: [Any]) {
    itemsBeingDragged = draggedItems.flatMap({ $0 as? MyItem })
}

func outlineView(_ outlineView: NSOutlineView, draggingSession session: NSDraggingSession, endedAt screenPoint: NSPoint, operation: NSDragOperation) {
    itemsBeingDragged = []
}

Наконец, я реализовал outlineView(_:acceptDrop:item:childIndex:) следующим образом:

func outlineView(_ outlineView: NSOutlineView, acceptDrop info: NSDraggingInfo, item: Any?, childIndex index: Int) -> Bool {
    guard
        (info.draggingSource() as? NSOutlineView) === outlineView,
        let dropTarget = item as? MyItem
        else { return false }

    // Update model using `dropTarget`, `index`, and `itemsBeingDragged`.
    // Update `outlineView` to match.
    return true
}

info.draggingSource() равно nil, если перетаскивание происходит из другого приложения. Если перетаскивание происходит из того же приложения, это объект, предоставляющий перетаскиваемые элементы. Итак, первое, что я делаю, это убеждаюсь, что перетаскивание происходит из того же контура, что и перетаскивание.

ОБНОВИТЬ

Если источник перетаскивания находится в другом приложении, нет смысла получать указатель на исходный перетаскиваемый элемент, поскольку указатели недействительны за пределами процесса — исходный перетаскиваемый элемент не существует в вашем процессе. Единственный осмысленный способ перетащить элемент за границы процесса — это фактически предоставить сериализованное представление перетаскиваемого элемента.

Если источник перетаскивания также находится в вашем приложении, вы можете поместить указатель на монтажный стол, но это все равно небезопасно для памяти. Лучше сделать что-то вроде определения протокола для получения перетаскиваемых элементов из источника:

protocol MyDragSource {
    var itemsBeingDragged: [Any]?
}

Затем в acceptDrop:

guard
    let sameAppSource = info.draggingSource(),
    let source = (sameAppSource as? MyDragSource) ?? (sameAppSource as? NSTableView)?.dataSource as? MyDragSource,
    let draggedItems = source.itemsBeingDragged?.flatMap({ $0 as? AcceptableItem }),
    draggedItems.count > 0
    else { return false }

…где AcceptableItem — это любой тип, который вы используете для элементов представления схемы.

Нет ничего постыдного в использовании info.draggingSource(). Это не просто так.

person rob mayoff    schedule 26.07.2017
comment
После стольких испытаний, включая info.enumerateDraggingItems, я не нашел другого пути лучше, чем этот. Спасибо. - person Edward Anthony; 26.07.2017
comment
Не используйте переменную, используйте монтажный стол. - person Willeke; 27.07.2017