如何为UITableView指定行单元格设置动态播放状态图像?
实现UITableView播放行的动态状态切换效果
最近开发音乐App时,需要实现这样的需求:
- 歌曲播放时,对应UITableView行显示动态效果
- 暂停播放时,动态效果停止
- 切换到其他歌曲时,原播放行恢复显示专辑封面
下面是我从初始想法到最终实现的整个过程:
初始尝试:直接用GIF作为动态图
一开始我想直接用GIF图来实现播放时的动态效果,写了这段未测试的代码,但后来发现这种方式在cell复用场景下控制起来不够灵活,而且没法精准控制播放/暂停状态:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell if resultSearchController.isActive { cell.addButton.tag = indexPath.row cell.songTitle.text = filteredTableData[indexPath.row].songName cell.songArtist.text = filteredTableData[indexPath.row].artistName cell.songArtwork.image = filteredTableData[indexPath.row].albumArtwork return cell } else { cell.addButton.tag = indexPath.row cell.songTitle.text = tableData[indexPath.row].songName cell.songArtist.text = tableData[indexPath.row].artistName cell.songArtwork.image = tableData[indexPath.row].albumArtwork return cell } // set image of specific tableView row cell to GIF image if indexPath.row == SongData.currentTrack { let image = UIImage(named: "playing-gif-image") cell.songArtwork.image = image } else { // do nothing } }
改进方案:用静态状态区分播放状态
之后我转向用静态图标区分不同状态(播放/暂停/默认),同时计划后续学习CAShapeLayer来实现自定义动态效果。这个方案逻辑更清晰,也更容易控制状态切换:
1. 完善数据模型(SongData.swift)
首先在数据模型中添加播放状态枚举,以及控制全局播放状态的静态变量:
import UIKit class SongData: NSObject, NSCoding { var songName: String var artistName: String var albumName: String var albumArtwork: UIImage var url: URL static var songList = [SongData]() static var shuffleSongList = [SongData]() static var currentTrack = 0 static var showCurrentPlayingSong = false static var repeatSequence = "repeatList" static var isPlaying = false enum PlayingCellState { case nonState case playing case paused } init(songName: String, artistName: String, albumName: String, albumArtwork: UIImage, url: URL) { self.songName = songName self.artistName = artistName self.albumName = albumName self.albumArtwork = albumArtwork self.url = url } // NSCoding相关实现省略... }
2. 自定义Cell添加状态配置方法(CustomCell.swift)
在自定义Cell中添加一个根据状态配置UI的方法,这样可以统一管理不同状态下的显示:
import UIKit class CustomCell: UITableViewCell { @IBOutlet weak var songTitle: UILabel! @IBOutlet weak var songArtist: UILabel! @IBOutlet weak var songArtwork: UIImageView! @IBOutlet weak var addButton: UIButton! override func awakeFromNib() { super.awakeFromNib() songArtwork.layer.cornerRadius = 8.0 } func config(forState state: SongData.PlayingCellState) { // 根据状态设置Cell显示 switch state { case .nonState: print("nonState") // 恢复默认专辑封面显示 case .playing: songArtwork.image = UIImage(named: "Play") case .paused: songArtwork.image = UIImage(named: "Pause") } } }
3. 表格控制器中处理状态判断(TableViewController)
添加一个方法来判断每个Cell对应的状态,然后在cellForRowAt中调用Cell的配置方法:
// 判断Cell对应的播放状态 func stateForCell(at indexPath: IndexPath) -> SongData.PlayingCellState { // 首次打开列表且无歌曲播放时,返回默认状态 if SongData.songList.count == 0 { return .nonState } else { if indexPath.row == SongData.currentTrack { return SongData.isPlaying ? .playing : .paused } else { return .nonState } } } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell if resultSearchController.isActive { cell.addButton.tag = indexPath.row cell.songTitle.text = filteredTableData[indexPath.row].songName cell.songArtist.text = filteredTableData[indexPath.row].artistName cell.songArtwork.image = filteredTableData[indexPath.row].albumArtwork } else { cell.addButton.tag = indexPath.row cell.songTitle.text = tableData[indexPath.row].songName cell.songArtist.text = tableData[indexPath.row].artistName cell.songArtwork.image = tableData[indexPath.row].albumArtwork } // 配置Cell的播放状态 cell.config(forState: stateForCell(at: indexPath)) return cell }
最终方案:用Lottie实现动态音频可视化效果
为了让播放状态的动态效果更生动,我引入了lottie-ios库来实现音频可视化动画,但目前遇到一个小问题:设置了循环模式后,动画仅播放一次,后续会排查具体原因。
更新后的CustomCell.swift代码
import UIKit import Lottie class CustomCell: UITableViewCell { @IBOutlet weak var songTitle: UILabel! @IBOutlet weak var songArtist: UILabel! @IBOutlet weak var songArtwork: UIImageView! @IBOutlet weak var view: UIView! @IBOutlet weak var addButton: UIButton! let animationView = AnimationView() override func awakeFromNib() { super.awakeFromNib() songArtwork.layer.cornerRadius = 8.0 } func playAnimation(){ let animation = Animation.named("366-equalizer-bounce") animationView.animation = animation // 这里遇到了问题:设置循环模式后动画只播放一次 animationView.loopMode = LottieLoopMode.repeat(3600.0) animationView.play() animationView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(animationView) NSLayoutConstraint.activate([ animationView.heightAnchor.constraint(equalTo: view.heightAnchor), animationView.widthAnchor.constraint(equalTo: view.widthAnchor) ]) } func config(forState state: SongData.PlayingCellState) { // 根据状态设置Cell显示 switch state { case .nonState: print("nonState") view.isHidden = true case .playing: view.isHidden = false playAnimation() case .paused: view.isHidden = false // 后续完善暂停时的显示逻辑 // songArtwork.image = UIImage(named: "Pause") } } }
内容的提问来源于stack exchange,提问作者Zhou Haibo
相关产品推荐
相关产品推荐

