I am developing a music app whereby users can browse from a selection of tracks and save them into a playlist. I am having an issue where if a track is saved into a playlist twice, once after the other, the play controls do not act as desired i.e. 2x track1 is added at the beginning of the playlist - if selecting 1st track1 - plays and pauses just fine, then if I select 2nd track1 (if 1st track1 has already been selected, or vice versa) it carries on playing/pausing as if it were the the 1st track1.
Is there a way to duplicate that item instead of referencing the same item? Here is some code that shows how I am populating playlists and my data structure etc.
Data Structure:
class Data: NSObject, NSCoding {
var title: String
var body: String
var colour: UIColor
var url: String
var isPlaying : Bool = false
init(title: String, body: String, colour: UIColor, url: String) {
self.title = title
self.body = body
self.colour = colour
self.url = url
}
class func createTrackArray() -> [Data] {
var array: [Data] = []
let track1 = Data(title: "Track 1 Title", body: "Track 1 Body", colour: .white, url: "track1url")
let track2 = Data(title: "Track 2 Title", body: "Track 2 Body", colour: .white, url: "track2url")
array.append(track1)
return array
}
}
Displaying Music To Browse Through:
//Global Variable
var musicDataArray: [Data] = []
class MusicVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
musicDataArray = Data.createTrackArray()
}
}
//MARK: - CollectionView Cell Configuration
extension MusicVC: UICollectionViewDelegate, UICollectionViewDataSource, CellDelegate {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return musicDataArray.count
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: .musicCell, for: indexPath) as! MusicCell
cell.cellData = musicDataArray[indexPath.item]
cell.song = musicDataArray[indexPath.item]
cell.cellDelegate = self
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
currentIndexPathItem = indexPath.item
guard let cellToPlay = musicCollectionView.cellForItem(at: IndexPath(item: currentIndexPathItem, section: 0)) as? MusicCell else {
print("Cell not here")
return
}
didSelectCell(for: cellToPlay)
prepareTrackForSavingToPlaylist(track: indexPath.item)
}
func didSelectCell(for cell: MusicCell) {
cell.play()
}
This function saves the index (and associated array item??) into a temporary array. I then send it via segue to another view controller whereby it is saved into a playlist. i.e. playlistArray will only ever have one item in it that it passes to next view controller.
func prepareTrackForSavingToPlaylist(track: Int) {
playlistArray.removeAll()
playlistArray.append(musicDataArray[track])
}
Next ViewController: Passed data (which is the information from that temporary array from the previous view controller) is then added to whatever playlist they select in the collectionview
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let passedData = Data(title: dataFromMusicVC.title, body: dataFromMusicVC.body, colour: dataFromMusicVC.colour, url: dataFromMusicVC.url)
playlistArray[indexPath.item].append(passedData)
}
And then finally the user can select a playlist and it will display all of the saved tracks in that playlist
extension UserPlaylistsVC: UICollectionViewDelegate, UICollectionViewDataSource, UserDelegate {
func didSelectCell(for cell: UserPlaylistsCell) {
cell.play()
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return playlistArray[playlistIndex].count
}
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 0
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let createID = "UserCell"
let userCell = collectionView.dequeueReusableCell(withReuseIdentifier: createID, for: indexPath) as! UserPlaylistsCell
//playlistIndex is just an Int that is based on which cell is selected in a different view controller so that the right playlist is accessed.
userCell.song = playlistArray[playlistIndex][indexPath.item]
userCell.cellData = playlistArray[playlistIndex][indexPath.item]
userCell.userDelegate = self
userCell.delegate = self
return userCell
}
This is the cell that is used for the tracks in the playlists:
protocol UserDelegate: class {
func didSelectCell (for cell: UserPlaylistsCell)
}
class UserPlaylistsCell: UICollectionViewCell {
@IBOutlet weak var titleLbl: UILabel!
@IBOutlet weak var bodyLbl: UILabel!
@IBOutlet weak var colourView: UIView!
var song : TypeData!
var audio = Audio()
weak var delegate: UserPlaylistDelegate?
weak var userDelegate: UserDelegate?
override func prepareForReuse() {
super.prepareForReuse()
}
override var isSelected: Bool {
didSet {
self.contentView.backgroundColor = isSelected ? UIColor.UMLightGrey : UIColor.white
}
}
override func awakeFromNib() {
super.awakeFromNib()
titleLbl.textColor = UIColor.UMDarkGrey
bodyLbl.textColor = UIColor.UMDarkGrey
colourView.layer.cornerRadius = colourView.layer.frame.size.width / 2
colourView.clipsToBounds = true
}
var cellData: TypeData! {
didSet {
titleLbl.text = cellData.title
bodyLbl.text = cellData.body
colourView.backgroundColor = cellData.colour
}
}
func play() {
if !(song?.isPlaying)! {
song?.isPlaying = true
//Checking whether the global variable is the same as the selected song url so that I don't have to fetch the asset again (fetching the asset plays the track from the beginning again)
if urlString == song.url {
player.play()
} else {
urlString = song.url
audio.fetchAsset()
audio.playAsset()
}
return
}
song?.isPlaying = false
player.pause()
print("Stop", (song?.title)!)
}
}
Do you have any idea why if the same track is saved twice in a row into the playlist and is selected to play - that the controls don't act like if it were a different track i.e. the desired functionality is that it would be treated as a completely different track where selecting it would play it from the beginning again whilst also retaining the ability to pause and play them individually.
If you need any more clarifications or extra code then please do not hesitate to ask. I really appreciate you taking the time to look over this problem.
EDIT
Here is the didSelectItemAt method as requested:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
currentItemIndex = indexPath.item
guard let cellToPlay = userPlaylistsCollectionView.cellForItem(at: IndexPath(item: currentItemIndex, section: 0)) as? UserPlaylistsCell else {
return
}
didSelectCell(for: cellToPlay)
}
EDIT
didDeselectMethod:
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
if let cellToStop = userPlaylistsCollectionView.dataSource?.collectionView(userPlaylistsCollectionView, cellForItemAt: indexPath) as? UserPlaylistsCell {
if (cellToStop.song.isPlaying) {
didSelectCell(for: cellToStop)
print(cellToStop.song.isPlaying)
}
}
}