iosswiftuitableviewmpmediaitem

Fetching large number of songs from MPMediaLibrary into a UITableView


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.


Solution

  • 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]
    }