I have a UITableView
with cells that display an audio waveform image and a playback button. Tapping the button causes the audio to be played back, of course. In order to reduce memory usage, I have a single instance of AVAudioPlayer
declared in my table view controller. I defined a protocol that has playAudio(url:URL)
and stopPlayingAudio()
methods and my table view controller conforms to this protocol. Anytime a new cell is dequeued, I assign the table view controller as the delegate for the cell so that when the user taps on the playback button in the cell UI, it calls the playAudio(url)
delegate method. This seems to be working well enough but I've run into a problem now.
I'm calculating a percent complete value as the audio is playing and I'd like to update the table view cell UI with this value but I'm not sure how to reference the correct cell from the table view controller. It seems like the cell that was tapped on to start the audio playback could end up getting recycled if it scrolls off the screen (unless I'm misunderstanding how cells get dynamically dequeued). Is there a way to do this?
There are a few ways in which you can achieve what you intend to achieve,
If you are maintaining a datasource to create a cell from(which you should if you are not), maintain the state of the cell, this can include the percentage played of the cell's url and the state whether the item isPlaying
, which will be false by default.
Once the states are in place, you need to now update this state, so you will have to add create a protocol(say AudioStateObserverProtocol
) for sending this data to the cell, this protocol may have a method which periodically updates the cell UI as the player plays (something like, updatePlayDuration:
or something of this sort), this will make sure that you get the value of how much of the asset has played. So when the user taps the play button instead of calling playAudio(url:URL)
you can update the protocol method to playAudio(url:URL, stateObserver: TheTableViewCell)
, which the table view controller will set to as the delegate of type AudioStateObserverProtocol
.
protocol AudioStateObserverProtocol {
func updatePlayDuration(to time: CMTime)
}
Add another protocol method stopObserving(cell: TheTableViewCell)
to the protocol you have defined with playAudio(url:URL)
and stopPlayingAudio()
The next step is how to make sure that the cell on reuse does not still receive/use the update, to do this you can make sure that when you setup the cell in your cellForRow
datasource methods you first call the stopObserving(cell: TheTableViewCell)
. In your implementation of this method inside the tableview controller check for the instance of the cell against the param of type AudioStateObserverProtocol
and if same, set it to nil so that this cell does not get the updates again.
One important thing to keep in mind here is that, if your audio is still playing then you need to make sure that when the cell for that index is getting created it show updates, this is when you will check the isPlaying
state of the datasource and if it is true set the cell as the observer of type AudioStateObserverProtocol