Search code examples
iosswiftavaudioplayeruitableview

Toggle tableview cell image when AVAudioPlayer finishes playing


I have a tableview and each cell has a play button. The play button is a UIButton that toggles between a play and pause image. When the play/pause button is pressed, AVAudioPlayer plays/pauses the audio file associated with the cell. As of now, I have the images toggling perfectly and in sync with the audio player. However, when the audio finishes playing, I'd like the image to toggle back to its default state - the play button image.

//View controller containing tableview
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

    let cell = self.audioTable.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! AudioCell

    let track = self.sectionTracks[indexPath.row]

    cell.playButton.addTarget(self, action: #selector(HomeController.playPausePressed(_:)), forControlEvents: UIControlEvents.TouchUpInside)

    var playButtonImage = UIImage(named: "TableViewCellPlayButton84.jpg")
    var pauseButtonImage = UIImage(named: "TableViewCellPauseButton84.jpg")
    playButtonImage = playButtonImage?.imageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal)
    pauseButtonImage = pauseButtonImage?.imageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal)

    cell.playButton.setImage(playButtonImage, forState: UIControlState.Normal)
    cell.playButton.setImage(pauseButtonImage, forState: UIControlState.Selected)
    cell.playButton.selected = false

    return cell
}

func playPausePressed(sender:UIButton) {

    let pointInTable: CGPoint = sender.convertPoint(sender.bounds.origin, toView: self.audioTable)
    let cellIndexPath = self.audioTable.indexPathForRowAtPoint(pointInTable)
    let track = self.sectionTracks[cellIndexPath!.row]

     //Handle AVAudioPlayer play/pause functionality
}

//Custom tableview cell file
@IBAction func playPausePressed(sender: UIButton!) {

    if (sender.selected)
    {
        sender.selected = false
    }
    else{
        sender.selected = true
    }
}

Solution

  • You can subscribe to be the delegate of the AVAudioPlayer and then check when the audio is complete. Can you play more than one file at a time or multiple? If you can play multiple you'll have to track all the playing audio and then toggle the button on the row based on when the delegate is fired and which row the player was associated with. If you can only play one then when the user starts playing the audio just save that row and when the audio ends toggle the button for that row.

    https://developer.apple.com/library/ios/documentation/AVFoundation/Reference/AVAudioPlayerDelegateProtocolReference/

    private var xoAssociationKey: UInt8 = 0
    
    extension AVAudioPlayer {
        var name: String! {
            get {
                return objc_getAssociatedObject(self, &xoAssociationKey) as? String
            }
            set(newValue) {
                objc_setAssociatedObject(self, &xoAssociationKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
            }
        }
    }
    

    It might be helpful to make an extension as above to store a name to the AVAudioPlayer instance. You can then keep track of the name of the audio that is playing and the cell's index path in a mutable dictionary. When you get the call back to the delegate function optional func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) you can look in the dictionary for the player.name which will give you back the index path to that button's cell. Get the cell from the table view and then toggle the buttons selected state.

    EDIT

    For a single cell playing at a time you could add a variable to you class as var playingAudioIndexPath:NSIndexPath?

    When the audio starts playing set this to the cell index path for the button that was clicked.

    Then on the playback finish event just grab the cell and toggle the button

    func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer,
                                 successfully flag: Bool) {
        guard let indexPath = self.playingAudioIndexPath,
            cell = self.table.cellForRowAtIndexPath(indexPath) as? AudioCell else {
             return;
         }
    
         cell.playButton.selected = false
         self.playingAudioIndexPath = nil
    }
    

    DMCApps