简体   繁体   中英

How to tell if I am using the same item to populate more than one UICollectionViewCell?

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 ie 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. ie 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 ie 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)
        }
    }
}

The problem could be here

if urlString == song.url {
        player.play()
    } else {
        urlString = song.url
        audio.fetchAsset()
        audio.playAsset()
    }

This is based on the fact that two songs have different url, but if the track are the same, they surely have the same url. Try different caching system, as example an Array that contains previous/currently played/ing song id (based on cell indexPath, for example).

Anyway, it's a bad behaviour to let user have the same track more than once in a playlist.

You're using class for your song objects. That means they're stored in memory by reference, so when you add the same song into the array twice, accessing either first or second instance will modify the same entity in memory. In your case I'd switch to using struct for your "Data". struct are value type and instances are copied instead of referenced. More on that in Swift docs

Also it's very important not to override system class names as Data is standard Swift type alias that you will most likely use beyond the logic of your song models.

I don't think this is an issue with multiple referencing to the same item. It seems like your entire data in the array are not updated accordingly when a single data in the array is updated. Maybe I couldn't find the logic in your codes. Disregard if so.

Let say you have two tracks added to a playlist and one is played. Then when you play the the other track, the fist one's isPaying value should be updated to 'false' while the second one is turned on. You should make sure if that's happening.

Few recommendations.

  1. Removes all the logics beside the logic to display data from the cell such as most of code you have in your play() function. Move the data update to where you can access the entire data. (Probably view controller for your case) Then update not only the status of that individual data but also entire data set so you have all the data synched up.

or

  1. Remove 'isPlaying' from Data class and let other manager or controller tracks the currently playing song so you don't need to worry about one or no song always playing.

  2. Trying to pass the meaningful data to each view controller. For example, it seems like your Last view controller doesn't need to know about other playlist. So rather than getting a song from playlistArray[playlistIndex][indexPath.item], it's better if you have a data structure like playlist[indexPath.item].

  3. Make sure your implementation is right when you do comparison between the object. Sounds like you can add two same songs to a same playlist and comparing song.url in play() function doesn't look promising. Even reference comparison sounds dangerous to me. What you should do? Hard to tell with given context. Again, it might be better if some other source let cell know if it's currently playing or not and the cell only displays its status.

Overall, your requirements don't look hard.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM