You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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 UITableView reuses 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 didSelectRowAt logic 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:

  1. Let your AudioListViewController track the currently playing row index (selectedRowIndex).
  2. In cellForRowAt, set the button's visibility based on whether the cell's index matches selectedRowIndex.
  3. When a cell is tapped, update selectedRowIndex and 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 from Bundle.main.path—this is incorrect; local file paths require URL(fileURLWithPath:).
  • The didSelectRowAt logic has a bug where you're grabbing songList[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:

  1. Move the AVAudioPlayer instance to your AudioListViewController so you can control playback centrally.
  2. When tapping a new cell, first stop the current player (if it's playing), then load and play the new audio.
  3. Fix the file path URL creation to use the correct initializer.
  4. Clean up the didSelectRowAt logic 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:

  1. Centralized Audio Management: Moved AVAudioPlayer to AudioListViewController so we can easily stop/start playback across cell taps.
  2. Track Selected Row: selectedRowIndex controls which cell shows the play button, and we refresh the table to update button states when selection changes.
  3. Fixed File Path: Used URL(fileURLWithPath:) instead of URL(string:) for local bundle files (critical for loading audio correctly).
  4. Cleaned Cell Logic: Removed redundant songs array and playback code from the cell—cells now only handle display and favorite toggling, leaving audio control to the view controller.
  5. Tapped Row Handling: When tapping the same row twice, we toggle play/pause instead of reloading the audio.

内容的提问来源于stack exchange,提问作者Niraj

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 05:17:44