I used UITableviewDiffableDataSource
with UITableView
to display songs from the music library. This code worked fine:
let tracks: [MPMediaItem] = MPMediaQuery.songs().items ?? []
self.dataSource.apply(section: 0, items: tracks)
But when I wrapped MPMediaItem
inside a custom Track struct
, I got this error: Fatal: supplied identifiers are not unique.
struct Track: Equatable, Hashable {
let item: MPMediaItem
var title: String? { item.title }
init(item: MPMediaItem) {
self.item = item
}
}
let items = MPMediaQuery.songs().items ?? []
let tracks: [Track] = items.map { Track(item: $0) }
self.dataSource.apply(section: 0, items: tracks)
MPMediaItem
has already conformed Equatable
and Hashable
so I think it should be fine if I use it in another struct which also conforms Equatable
and Hashable
(Track struct
).
Update 1: apply(section:items:)
is an extension I added to UITableViewDiffableDataSource
for convenient:
extension UITableViewDiffableDataSource {
func apply(section: SectionIdentifierType, items: [ItemIdentifierType], animatingDifferences: Bool = false) {
var snapshot = NSDiffableDataSourceSnapshot<SectionIdentifierType, ItemIdentifierType>()
snapshot.appendSections([section])
snapshot.appendItems(items)
apply(snapshot, animatingDifferences: animatingDifferences)
}
}
Update 2: It worked after I conformed Track
to Identifiable
protocol:
struct Track: Equatable, Hashable, Identifiable {
let item: MPMediaItem
let id: MPMediaEntityPersistentID
var title: String? { item.title }
init(item: MPMediaItem) {
self.item = item
self.id = item.persistentID
}
}
or even changed title
to store property also worked without any errors:
struct Track: Equatable, Hashable {
let item: MPMediaItem
let title: String?
init(item: MPMediaItem) {
self.item = item
self.title = item.title
}
}
What makes it so different between these cases? And why do I got the error when using MPMediaItem
as the only store property of the Track
struct? Thanks in advance!
I'm going to guess that there's a bug in MPMediaItem's hashability. This can cause you to get different answers for the two situations you have described. In this example, I'll deliberately make a buggy NSObject:
class Dog : NSObject {
let name : String?
init(name:String?) {self.name = name}
override func isEqual(_ object: Any?) -> Bool {
if let dog = object as? Dog {
return self.name == dog.name
}
return false
}
}
struct DogHolder : Hashable {
let dog : Dog
var name : String? { dog.name }
}
Here's a test:
var set = Set<DogHolder>()
let dh1 = DogHolder(dog:Dog(name:"rover"))
let dh2 = DogHolder(dog:Dog(name:"rover"))
set.insert(dh1)
set.insert(dh2)
print(set.count)
do {
var set = Set<Dog>()
let dh1 = Dog(name:"rover")
let dh2 = Dog(name:"rover")
set.insert(dh1)
set.insert(dh2)
print(set.count)
}
Run the test over and over. Sometimes I get 1 and 2. Sometimes I get 2 and 1. Sometimes I get 1 and 1. Sometimes I crash.
I don't know what the precise problem is, but evidently exposing the NSObject hashability to Swift's hashability requirements exposes the bug. I would suggest reporting this to Apple and meanwhile continue to use a workaround such as your identifier.