0

I have a list of songs which should be played in each of row in table view by tapping on the button. I've made a button in custom cell and a protocol which used to display the tap in our view controller. For ex. when we tap on second button in cell, it should play second song, when we tap on third button, its play third song.

My ViewController:

import UIKit
import AVFoundation

class BeatPackViewController: UIViewController {
    
    @IBOutlet weak var beatTableView: UITableView!
    @IBOutlet weak var coverImage: UIImageView!
    @IBOutlet weak var looppackNameLabel: UILabel!
    @IBOutlet weak var producerNameLabel: UILabel!
    @IBOutlet weak var backButtonLabel: UIButton!

    let data = DataLoader().beatData
    var songs: [String] = []
    
    var audioPlayer = AVAudioPlayer()
    
    func getSongs() -> [String] {
        var names: [String] = []
        let path = Bundle.main.resourceURL?.appendingPathComponent("songs")
        do {
            let songs = try FileManager.default.contentsOfDirectory(at: path!, includingPropertiesForKeys: nil, options: FileManager.DirectoryEnumerationOptions.skipsHiddenFiles)
            
            for song in songs {
                let strArray = song.absoluteString.components(separatedBy: "/")
                var songName = strArray[strArray.count - 1].replacingOccurrences(of: "%20", with: " ")
                songName = songName.replacingOccurrences(of: ".wav", with: "")
                names.append(songName)
            }
        } catch {}
        
        return names
    }
    
    func playSong(index: Int) {
        do {
            let songPath = Bundle.main.path(forResource: songs[index], ofType: ".wav", inDirectory: "songs")
            try audioPlayer = AVAudioPlayer(contentsOf: URL(fileURLWithPath: songPath!))
            audioPlayer.play()
        } catch {}
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        beatTableView.delegate = self
        beatTableView.dataSource = self
        
        self.view.backgroundColor = SettingsService.sharedService.backgroundColor
        coverImage.layer.cornerRadius = 20
        coverImage.layer.shadowRadius = 7
        coverImage.layer.shadowOpacity = 0.8
        coverImage.layer.shadowOffset = CGSize(width: 3, height: 3)
        coverImage.layer.shadowColor = UIColor.black.cgColor
        coverImage.clipsToBounds = true
    }
    
}



extension BeatPackViewController: UITableViewDataSource, UITableViewDelegate {
    
    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 80
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return data.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = beatTableView.dequeueReusableCell(withIdentifier: "firstLoopCell", for: indexPath as IndexPath) as! CustomLoopsCell
        cell.loopNameLabel.text = data[indexPath.row].loop_name
        cell.delegate = self
        cell.playButtonOutlet.addTarget(self, action: #selector(CustomLoopsCell.playButtonPressed(_:)), for: .touchUpInside)
        //        cell.instrumentLabel.text = data[indexPath.row].loops[indexPath.row].Instrument
        //        cell.producerLabel.text = data[indexPath.row].loops[indexPath.row].producer
        return cell
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        didTapButton()
    }
    
}

extension BeatPackViewController: CustomLoopsDelegate {
    func didTapButton() {
    }
    
}

My CustomCell

import UIKit
import AVFoundation

protocol CustomLoopsDelegate: AnyObject {
    func didTapButton()
}

class CustomLoopsCell: UITableViewCell {
    
    weak var delegate: CustomLoopsDelegate?
    
    var songs: [String] = []
    
    var audioPlayer = AVAudioPlayer()
    
    
    @IBOutlet weak var loopNameLabel: UILabel!
    @IBOutlet weak var producerLabel: UILabel!
    @IBOutlet weak var instrumentLabel: UILabel!
    @IBOutlet weak var playButtonOutlet: UIButton!
    
    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }
    
    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)
        
        // Configure the view for the selected state
    }
    
    @IBAction func playButtonPressed(_ sender: UIButton) {
        delegate?.didTapButton()
    }
}
mozeX
  • 23
  • 7
  • Can you please describe what's the problem you are facing? – Dhara Patel Jul 16 '21 at 10:57
  • 1
    first of all remove `didTapButton` from `didSelectRowAt`. – jatin fl Jul 16 '21 at 10:57
  • 2
    You can't add the button tap handler in `cellForRowAt` because cells are reused as you scroll and you will end up adding multiple tap handlers to the same button. Use one of these techniques. https://stackoverflow.com/questions/28659845/how-to-get-the-indexpath-row-when-an-element-is-activated/38941510#38941510 – Paulw11 Jul 16 '21 at 11:01
  • The problem is that I figured out how to make tappable button in our cell, but how I can do that when we tap on each button in each of raw and that it will play different sound from songs array? – mozeX Jul 16 '21 at 11:04
  • What is a guy named 'data' and where does it come from? – El Tomato Jul 16 '21 at 11:29
  • @ElTomato its from file with json data – mozeX Jul 16 '21 at 11:39
  • What kind of information does 'data' contain? – El Tomato Jul 16 '21 at 11:48
  • let loop_name: String let Instrument: String let loop_link: String let producer: String – mozeX Jul 16 '21 at 11:57

1 Answers1

0

as Paulw11 said don't use btn action in cellForRowAt you need to add delegate. check code i tried regarding yours. here you don't need didSelect method

BeatPackViewController.Swift

class BeatPackViewController: UIViewController, CustomLoopsDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell : CustomLoopsCell = tableView.dequeueReusableCell(withIdentifier: "firstLoopCell", for: indexPath) as! CustomLoopsCell
        cell.delegate = self
        return cell
    }
    
    func btnUseTap(cell: CustomLoopsCell) {
        let indexPath = self.tableview.indexPath(for: cell)
        //play song here
    }

}

CustomLoopsCell.Swift

protocol CustomLoopsDelegate: AnyObject {
    func btnUseTap(cell: CustomLoopsCell)
}

class CustomLoopsCell: UITableViewCell {
    
    weak var delegate: CustomLoopsDelegate?
    
    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

        // Configure the view for the selected state
    }
    
    @IBAction func btnUse(_ sender: Any) {
        delegate?.btnUseTap(cell: self)
    }
    
}

Update song play code

let audioData = try! Data(contentsOf: url, options: .mappedIfSafe)
        try! AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback)
        try! AVAudioSession.sharedInstance().setActive(true)
        do {
            self.audioPlayer = try AVAudioPlayer(data: audioData, fileTypeHint: AVFileType.m4a.rawValue)
            self.audioPlayer.prepareToPlay()
            self.audioPlayer.play()
            if isVideoMute {
                self.audioPlayer.volume = 0
            }else {
                self.audioPlayer.volume = 1
            }
        } catch let error {
            print(error.localizedDescription)
        }
jatin fl
  • 157
  • 6
  • Fixed this now, but now how I can play song in each of the rows by clicking this button which I have? – mozeX Jul 16 '21 at 11:21
  • can you see func `btnUseTap` there you find `index` which you select from table. play song there from index. – jatin fl Jul 16 '21 at 11:22
  • I've used there this methods playSong(index: indexPath!.row) and I've got an error "Index out of range" – mozeX Jul 16 '21 at 11:23
  • make sure `songs` array can't be nil and also check index you get is correct? – jatin fl Jul 16 '21 at 11:26
  • I'm not sure, could you check if my method to play songs is correct, I've had a folder inside my project which named "Songs" and method in view controller – mozeX Jul 16 '21 at 11:31
  • @mozeX check my code i used it this way. you need to change file hint there cause you used `.wav` file an i used `.mp4` – jatin fl Jul 16 '21 at 11:35
  • it looks good! But where you get this "url"? – mozeX Jul 16 '21 at 11:49
  • from device where i stored songs. as i seen you get from bundle so you directly get by name. – jatin fl Jul 16 '21 at 12:05
  • Need to change .wav in the each of songs in folder or how? – mozeX Jul 16 '21 at 12:18
  • no need to change formate of song change in my last code this line `self.audioPlayer = try AVAudioPlayer(data: audioData, fileTypeHint: AVFileType.m4a.rawValue)` – jatin fl Jul 16 '21 at 12:19
  • func playSong(index: Int) { do { let songPath = Bundle.main.path(forResource: songs[index], ofType: ".wav", inDirectory: "songs") let audioData = try! Data(contentsOf: URL(fileURLWithPath: songPath!), options: .mappedIfSafe) try! AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback) try! AVAudioSession.sharedInstance().setActive(true) do { self.audioPlayer = try AVAudioPlayer(data: audioData, fileTypeHint: AVFileType.wav.rawValue) – mozeX Jul 16 '21 at 12:25
  • I changed it to this and again index out of range – mozeX Jul 16 '21 at 12:38
  • did you check array and index here `let indexPath = self.tableview.indexPath(for: cell)`? – jatin fl Jul 16 '21 at 12:40
  • I have now like this func btnUseTap(cell: CustomLoopsCell) { let indexPath = self.beatTableView.indexPath(for: cell) playSong(index: indexPath!.row) – mozeX Jul 17 '21 at 11:04
  • Hey! I've fixed this, now everything works, thanks! Also, maybe you how I can make the same, but from local JSON file? – mozeX Jul 17 '21 at 11:41