I have a table view containing the list of all songs in the music library. At first I was fetching all the songs and saving their info into an array like this:
var songs = [Song]()
private func loadSongs(){
let itunesSongs = MPMediaQuery.songs().items
if itunesSongs == nil {
return;
}
for song in itunesSongs!{
let artist = song.albumArtist
let trackTitle = song.title
let image = song.artwork?.image(at: CGSize(width: 90, height: 90))
let url = song.assetURL?.absoluteString ?? ""
let song1 = Song(title: trackTitle ?? "", artist: artist ?? "", image: image, url: url)
songs.append(song1)
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "SongTableViewCell"
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? SongCell else {
fatalError("The dequeued cell is not an instance of SongCell.")
}
let song = playerManager.songs[indexPath.row]
cell.setAttributes(song: song)
cell.preservesSuperviewLayoutMargins = false
return cell
}
When I was testing this on a device with more than 10000 songs, It took about 5-10 seconds to launch the App. So I modified the way I'm populating the table view as follows:
var itunesSongs: MPMediaQuery.songs().items
func getSong(index: Int) -> Song {
if(index >= songs.count){
let song = itunesSongs![index]
let ans = Song(title: song.title ?? "", artist: song.albumArtist ?? "", image: song.artwork?.image(at: CGSize(width: 90, height: 90)), url: song.assetURL?.absoluteString ?? "")
songs.append(ans)
}
return songs[index]
}
So I would use getSong()
in tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)
instead of using songs
array.
The problem was solved and the App launched normally. However, Occasionally, I would get Fatal error: Index out of range
on return songs[index]
line.
This happens when I scroll fast and it happens with a different index every time. I tried to fetch a 100 song instead of 1 at a time but that didn't solve it either.
I'm thinking of using a background thread to fill the songs
array but not sure if this is the right way to do it.
The Problem is that the table view is not calling getSong()
sequentially. For example, the table view could call getSong(6)
then getSong(3)
. I updated the function to:
func getSong(index: Int) -> Song {
while (index >= songs.count){
let song = itunesSongs![songs.count]
let ans = Song(title: song.title ?? "", artist: song.albumArtist ?? "", image: song.artwork?.image(at: CGSize(width: 90, height: 90)), url: song.assetURL?.absoluteString ?? "")
songs.append(ans)
}
return songs[index]
}