Is kind of difficult to explain without knowing more about the rest of the app. It's important to give a little more context. I'll try to give you an answer assuming certain things.
First I'll asume you have Song objects that looks like this:
public struct Song: Equatable {
let name: String
let fileName: String
}
Your database has the following public methods and property:
class DB {
func getSong(_ position: Int) -> Song?
func getPosition(_ song: Song) -> Int?
var count: Int
}
To make it easy for this sample code on the init some predefine data is initialize.
Also there is a Player object that manages playing audio with the following public methods:
class Player {
func currentlyPlaying() -> Song?
func play(this song: Song)
func stop()
}
Now with this previously defined I created a custom cell to display the name and a button for each Song in the database. The definition is this:
class CustomCell : UITableViewCell {
@IBOutlet weak var label: UILabel!
@IBOutlet weak var button: UIButton!
}
And looks like:

Next let's define the tableview's datasource methods. In each cell a target is added for each button's touchUpInside event (as you defined it in the question).
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return DB.shared.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell",
for: indexPath) as! CustomCell
cell.label.text = DB.shared.getSong(indexPath.row)!.name
cell.button.addTarget(self, action: #selector(buttonTapped(_:)),
for: .touchUpInside)
return cell
}
Next let's define a helper method to locate a UIVIew inside a TableView. With this method we can get the IndexPath of any control inside any cell in a TableView. The return value is optional in order to return nil if not found.
func getViewIndexInTableView(tableView: UITableView, view: UIView) -> IndexPath? {
let pos = view.convert(CGPoint.zero, to: tableView)
return tableView.indexPathForRow(at: pos)
}
Another helper method was define to change the image of a button with an animation:
func changeButtonImage(_ button: UIButton, play: Bool) {
UIView.transition(with: button, duration: 0.4,
options: .transitionCrossDissolve, animations: {
button.setImage(UIImage(named: play ? "Play" : "Stop"), for: .normal)
}, completion: nil
}
A method was necessary to stop any current playing song. The first thing is to check wether a song is playing, if so call Player's stop method. Then let's localize the position of the Song in the database, which in my case corresponds to the position in the TableView; having this let's create a IndexPath to get the corresponding cell, finally call changeButtonImage with the cell's button to change the image.
func stopCurrentlyPlaying() {
if let currentSong = Player.shared.currentlyPlaying() {
Player.shared.stop()
if let indexStop = DB.shared.getPosition(currentSong) {
let cell = tableView.cellForRow(at: IndexPath(item: indexStop, section: 0)) as! CustomCell
changeButtonImage(cell.button, play: true)
}
}
}
The buttonTapped method which starts playing a song have some logic inside. First the method signature needs @objc in order to be used in the addTarget method. The logic is a follows:
- Localize the button's
IndexPath in the table view using the helper method.
- Localize the song in the database, the row number in the table corresponds to the order in the database.
- If there is a song currently playing and is the same as the one localize for the button tapped it means we just want to stop the song, so
stopCurrentlyPlaying is called and the method returns.
- If is not the same song or nothing is playing let's call:
stopCurrentlyPlaying, start playing the new song and change the tapped button's image to a Stop image.
The code looks like this:
@objc func buttonTapped(_ sender: UIButton) {
// Let's localize the index of the button using a helper method
// and also localize the Song i the database
if let index = getViewIndexInTableView(tableView: tableView, view: sender),
let song = DB.shared.getSong(index.row) {
// If a the song located is the same it's currently playing just stop
// playing it and return.
guard song != Player.shared.currentlyPlaying() else {
stopCurrentlyPlaying()
return
}
// Stop any playing song if necessary
stopCurrentlyPlaying()
// Start playing the tapped song
Player.shared.play(this: song)
// Change the tapped button to a Stop image
changeButtonImage(sender, play: false)
}
}
Here is a little video of the sample app working: sample app