Swift开发:AVAudioPlayer无法停止播放及UITableView按钮显示异常问题
Hey there, let's break down and fix those two frustrating issues you're facing with your audio player. I'll walk you through the root causes and step-by-step fixes, then share the revised code.
Issue 1: Button Display Logic Not Working as Expected
What's Wrong:
- Your cells are handling their own button visibility state, but since
UITableViewreuses cells, this state gets messed up when scrolling or tapping new rows. - You're not properly tracking the currently playing row in your view controller, and the
didSelectRowAtlogic is trying to dequeue a new cell instead of updating the existing selected one. - The button visibility isn't set when the cell is first created/reused in
cellForRowAt.
Fixes:
- Let your
AudioListViewControllertrack the currently playing row index (selectedRowIndex). - In
cellForRowAt, set the button's visibility based on whether the cell's index matchesselectedRowIndex. - When a cell is tapped, update
selectedRowIndexand refresh the table to update button states across all cells.
Issue 2: Audio Playback & Stop Functionality Broken
What's Wrong:
- Each cell has its own
AVAudioPlayer, which means when you tap a new cell, the old player keeps playing (you can't access it to stop it). - You're using
URL(string:)for a local file path fromBundle.main.path—this is incorrect; local file paths requireURL(fileURLWithPath:). - The
didSelectRowAtlogic has a bug where you're grabbingsongList[indexPath.section].id(which always pulls the first song's ID since your table only has one section) instead of using the tapped row's index.
Fixes:
- Move the
AVAudioPlayerinstance to yourAudioListViewControllerso you can control playback centrally. - When tapping a new cell, first stop the current player (if it's playing), then load and play the new audio.
- Fix the file path URL creation to use the correct initializer.
- Clean up the
didSelectRowAtlogic to properly update the selected row and trigger playback.
Revised Full Code
import UIKit import AVFoundation struct Audio { let id: Int let trackName: String let trackTitle: String let creationDate = Date() let album: String let uploadedBy: String let genre: String let lyricsLang: String let artist: String let duration: Float var completed = false } class AudioListViewController: UIViewController { // Track the currently playing row index var selectedRowIndex = -1 var songList: [Audio] = [ Audio(id: 0, trackName: "Song 1", trackTitle: "ABbcd:", album: "Loreim loreim", uploadedBy: "A men", genre: "Romantic", lyricsLang: "English", artist: "God", duration: 2.00), Audio(id: 1, trackName: "Song 2", trackTitle: "ABbcd:", album: "Loreim loreim", uploadedBy: "A men", genre: "Romantic", lyricsLang: "English", artist: "God", duration: 2.00), Audio(id: 2, trackName: "Song 3", trackTitle: "ABbcd:", album: "Loreim loreim", uploadedBy: "A men", genre: "Romantic", lyricsLang: "English", artist: "God", duration: 2.00) ] @IBOutlet weak var songListTableView: UITableView! // Central audio player managed by the view controller var songPlayer: AVAudioPlayer? @IBOutlet weak var close: UIButton! override func viewDidLoad() { super.viewDidLoad() songListTableView.dataSource = self songListTableView.delegate = self title = "Back" navigationItem.backButtonTitle = "Back be?" songListTableView.register(UINib(nibName: "SongsListTableViewCell", bundle: nil), forCellReuseIdentifier: "SongListCell") // Setup audio session once here do { try AVAudioSession.sharedInstance().setMode(.default) try AVAudioSession.sharedInstance().setActive(true, options: .notifyOthersOnDeactivation) } catch { print("Failed to setup audio session: \(error)") } } @IBAction func closeSe(_ sender: Any) { self.dismiss(animated: true, completion: nil) } // Helper method to play audio for a given index func playAudio(at index: Int) { // Stop current playback first songPlayer?.stop() let song = songList[index] let songId = String(song.id) guard let urlPath = Bundle.main.path(forResource: songId, ofType: "mp3"), let url = URL(fileURLWithPath: urlPath) else { print("Audio file not found for song ID: \(songId)") return } do { songPlayer = try AVAudioPlayer(contentsOf: url) songPlayer?.volume = 0.5 songPlayer?.play() } catch { print("Error initializing audio player: \(error)") } } // Helper method to stop audio playback func stopAudio() { songPlayer?.stop() songPlayer = nil } } extension AudioListViewController: UITableViewDataSource { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return songList.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "SongListCell", for: indexPath) as! SongsListTableViewCell let song = songList[indexPath.row] cell.songTitle?.text = song.trackName cell.songDuration?.text = String(song.duration) // Set button visibility based on selected row cell.useSong.isHidden = (indexPath.row != selectedRowIndex) return cell } } extension AudioListViewController: UITableViewDelegate{ func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) let tappedRow = indexPath.row // If tapping the already playing row, toggle playback if tappedRow == selectedRowIndex { if let player = songPlayer, player.isPlaying { player.pause() } else { playAudio(at: tappedRow) } return } // Update selected row and refresh table to update buttons selectedRowIndex = tappedRow tableView.reloadData() // Play the new audio playAudio(at: tappedRow) } } class SongsListTableViewCell: UITableViewCell { @IBOutlet weak var songTitle: UILabel! @IBOutlet weak var songDuration: UILabel! @IBOutlet weak var makeFavorite: UIButton! @IBOutlet weak var useSong: UIButton! override func awakeFromNib() { super.awakeFromNib() makeFavorite.addTarget(self, action: #selector(makeFavourite(_:)), for: .touchUpInside ) useSong.layer.cornerRadius = 6 useSong.isHidden = true // Hide by default } @IBAction func makeFavourite(_ sender: UIButton) { sender.isSelected = !sender.isSelected let imageName = sender.isSelected ? "star.fill" : "star" makeFavorite.setImage(UIImage(systemName: imageName), for: .normal) } // Optional: Add a delegate protocol here if you want the cell's play button to notify the VC for playback control }
Key Changes Explained:
- Centralized Audio Management: Moved
AVAudioPlayertoAudioListViewControllerso we can easily stop/start playback across cell taps. - Track Selected Row:
selectedRowIndexcontrols which cell shows the play button, and we refresh the table to update button states when selection changes. - Fixed File Path: Used
URL(fileURLWithPath:)instead ofURL(string:)for local bundle files (critical for loading audio correctly). - Cleaned Cell Logic: Removed redundant
songsarray and playback code from the cell—cells now only handle display and favorite toggling, leaving audio control to the view controller. - Tapped Row Handling: When tapping the same row twice, we toggle play/pause instead of reloading the audio.
内容的提问来源于stack exchange,提问作者Niraj
相关产品推荐
相关产品推荐

