Todd Page
Todd Page

Reputation: 51

Populate UITableView with Sections

I'm trying to populate TableView with sections. Where an artist is the Section Name and all songs of that artist are listed below. In total over 100 artists. Like so

FirstArtist
- Song 1
- Song 2

SecondArtist
 - Song 1
 - Song 2

OneMoreArtist...

I have an array of objects Song

struct Song {

  let songName: String?
  let album: String?
  let artist: String?

}

var songs = [Song]()

And an array of all artists names

var artists = [String]()

As I understand, to populate tableView with sections I need a Dictionary with Artist as key and array of Songs as value, like so

var toShow = [String : [Song]]()

So, then I'm trying to loop

    for artist in artists {
      for song in songs {
        if song.artist == artist {

          toShow[artist] = [song]

//          toShow[artist]?.append(song)

        }
      }
    }

But it doesn't work.

Probably I'm going wrong way.

What is the solution in this situation?

Thanks

Update

Made a stuct

    struct ArtistWithSongs  { 
       let name: String
       let songs: [Song]

        init(name: String, songs: [Song]) {
      self.name = name
      self.songs = songs
    }   
}

and trying to loop

var artistWithSongs = [ArtistWithSongs]() 

     for artist in artists {
          for song in songs {
            if artist == song.artist {
              artistWithSongs.append(ArtistWithSongs(name: song, songs: [song]))
            }
          }
        }

But apparently, my variant of looping is not correct. Now I'm getting an array of objects contains duplicate keys and only one Song per Key. It looks like

[Atrist1 : [Song1], Artist1 : [Song2], Artist1 : [Song3], ...]

My question is - What is the right way to make a loop, or is it possible somehow to merge objects with an identical key inside a Dictionary to get this [Artist1 :[Song1, Song2, Song3]]?

Upvotes: 1

Views: 73

Answers (3)

Todd Page
Todd Page

Reputation: 51

Thanks to everyone, I found a solution here, it worked for what I was trying to achieve. Roughly like this

let toShow = artistWithSongs.reduce([ArtistWithSongs](), { partialResult, artist in

    var dupe = partialResult.filter {$0.songName == group.name }.first
    if let dupeArtist = dupe {
        dupeArtist.songs?.append(contentsOf: artist.songs ?? [])
        return partialResult
    } else {
        var newPartialResult = partialResult
        newPartialResult.append(group)
        return newPartialResult
    }
})

Upvotes: 2

Dan Karbayev
Dan Karbayev

Reputation: 2920

If you want to transform an array of songs ([Song]) to a dictionary ([String : [Song]]), I would recommend using functional style:

self.toShow = songs.reduce([String: [Song]]()) { acc, next in
  // mutable copy
  var acc = acc

  // since artist is optional we need to substitute it with non-optional value if it's nil
  let artist = next.artist ?? "Unknown Artist"

  // if dictionary already has this artist, then append the `next` song to an array
  if acc[artist] != nil {
    acc[artist]?.append(next)
  } else {
  // otherwise create new array
    acc[artist] = [next]
  }

  return acc
}

In your table's data source provide the number of sections:

func numberOfSections(in tableView: UITableView) -> Int {
  return self.toShow.keys.count
}

And then you'll face a trouble: for number of rows in section you'll need to know which artist is the section stands for. So, I suggest you to use one more structure, an array of artists so that you keep the order of sections consistent:

self.artists = Array(self.toShow.keys).sorted()

This way, you can provide your table with following numbers of sections and rows:

func numberOfSections(in tableView: UITableView) -> Int {
  return self.artists.count
}

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  let artist = self.artists[section] // get the artist's name
  return self.toShow[artist]?.count ?? 0
}

Upvotes: 1

Shehata Gamal
Shehata Gamal

Reputation: 100503

In addition to your Song struct

struct Song {
  let songName: String?
  let album: String?
}

create Artist one like

struct Artist { 
   let name:String
   let songs = [Song]()
}

Then create an array of them

var artists = [Artist]()
let s1 = Song(songName:"name1",album:"alb1")
let s2 = Song(songName:"name2",album:"alb2")
let artist1 = Artist(name:"art",songs:[s1,s2])
artists.append(artist1)

In numberOfSections return

artists.count

In numberOfRows return

artists[section].songs.count 

In cellForRowAt access

let song = artists[indexPath.section].songs[indexPath.row]

Upvotes: 1

Related Questions