Thank you Quinn! Yes, the reason is that I use the FileManagerItemModel for drag & drop operations with a custom preview. For .onDrag(data, preview), I need to pass the data parameter as a closure returning an NSItemProvider. So in the closure, I create the NSItemProvider as NSItemProvider(object: item), where item is of type FileManagerItemModel.
And in order to do that, the FileManagerItemModel needs to implement NSItemProviderWriting. And that requires the FileManagerItemModel to be derived from NSObject (and to be a class instead of a struct too).
So, now I manage equality in FileManagerItemModel like so:
class FileManagerItemModel: NSObject, NSCopying, NSItemProviderReading, NSItemProviderWriting, Identifiable, Codable, Transferable {
// MARK: - Identification. The items are identified by type, rootURL and filePath.
// MARK: Swift Identifiable and Equatable
var id: URL
static func == (lhs: FileManagerItemModel, rhs: FileManagerItemModel) -> Bool {
lhs.id == rhs.id
}
// MARK: ObjC equality
override var hash: Int {
id.absoluteString.hashValue
}
override func isEqual(_ object: Any?) -> Bool {
guard let other = object as? FileManagerItemModel else { return false }
return self.hashValue == other.hashValue
}
...
init(type: FileManagerItemType, rootURL: URL, filePath: String) {
self.type = type
self.rootURL = rootURL
self.filePath = filePath
self.id = FileManagerItemModel.normalizedFileURL(type: type,
rootURL: rootURL,
filePath: filePath)
}
...
private static func normalizedFileURL(type: FileManagerItemType, rootURL: URL, filePath: String) -> URL {
switch type {
case .genericFolder(_):
return rootURL.appending(folderPathString: filePath)
default:
return rootURL.appending(filePathString: filePath) ?? rootURL
}
}
...
}
I'm sure this will not be the final implementation, but it works for now.
Note that this is a pet project that I am using to learn things, while at the same time having something useful in the end (a two-window file manager, a bit like Norton Commander). So my design decisions are not always the best. My main objective is to find out what works and what does not.
In-app multi-item drag&drop between two different Views is currently a bit of a difficult thing, when using SwiftUI. Let's just say that I have been learning a lot. ;)
Post
Replies
Boosts
Views
Activity
Thank you all people who viewed and maybe put some thought in this. I did not stop thinking about it myself, and found out what the issue was.
Basically I was believing that Identifiable was used to identify single items in the collection (Array), but it is actually Hashable. So it's not the id that's used as the identification of an item in the collection.
A part of my app that I did not show (I didn't want to make this question more TL;DR than it already was), is that: if a selection is made, I update my datasource.
That means that my datasource has a new bunch of FileManagerItemModel items, which all have the same id as in the collection, but not the same hashValue. And so, even though the ids are the same as before, the items could not be matched anymore because the hashValues changed.
The quick solution is to add this to FileManagerItemModel:
override var hash: Int {
id.absoluteString.hashValue
}
And change the .contains check into this:
multiSelectedItems.contains(where: { $0.hashValue == item.hashValue })
But this is not going to be the final implementation. Now that I understand my mistake, I should be able to make a cleaner solution.
Thank you for thinking with me. Even though nobody had time to reply anything, surely one of you thought 'maybe hash value?', and synchronicity took care of the rest. ;)