Swift中Collection View视频Cell复用引发CPU占用过高问题求助
CollectionView视频列表CPU占用过高问题及解决方案
我在Swift的CollectionView Cell中实现了视频播放功能,已经做到同一时间只播放一个视频,但滚动列表时CPU占用率飙升到200%以上,严重影响设备性能。另外,原本打算把播放暂停逻辑绑定到Cell的XIB按钮上,但不知道怎么正确处理,现在需要解决CPU高负载的问题。
当前代码
VideoCollectionViewCell代码
import UIKit import AVFoundation class VideoCollectionViewCell: UICollectionViewCell { var player: AVPlayer? var playerLayer: AVPlayerLayer? var playPauseButton: UIButton! override func awakeFromNib() { super.awakeFromNib() // Setup player layer playerLayer = AVPlayerLayer() playerLayer?.videoGravity = .resizeAspect contentView.layer.addSublayer(playerLayer!) // Setup play/pause button playPauseButton = UIButton(type: .system) playPauseButton.setTitle("Play", for: .normal) playPauseButton.addTarget(self, action: #selector(playPauseTapped), for: .touchUpInside) contentView.addSubview(playPauseButton) // Layout button playPauseButton.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ playPauseButton.centerXAnchor.constraint(equalTo: contentView.centerXAnchor), playPauseButton.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), playPauseButton.widthAnchor.constraint(equalToConstant: 100), playPauseButton.heightAnchor.constraint(equalToConstant: 50) ]) } override func layoutSubviews() { super.layoutSubviews() playerLayer?.frame = contentView.bounds } func configure(with url: URL) { player = AVPlayer(url: url) playerLayer?.player = player } @objc func playPauseTapped() { if player?.timeControlStatus == .playing { pause() } else { play() } } func play() { player?.play() playPauseButton.setTitle("Pause", for: .normal) } func pause() { player?.pause() playPauseButton.setTitle("Play", for: .normal) } override func prepareForReuse() { super.prepareForReuse() player?.pause() playerLayer?.player = nil } }
ViewController代码
import UIKit class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource { @IBOutlet weak var collectionView: UICollectionView! var videoURLs: [URL] = [/* Your video URLs here */] var currentlyPlayingIndex: IndexPath? override func viewDidLoad() { super.viewDidLoad() collectionView.delegate = self collectionView.dataSource = self collectionView.register(UINib(nibName: "VideoCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "VideoCell") } func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return videoURLs.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "VideoCell", for: indexPath) as! VideoCollectionViewCell cell.configure(with: videoURLs[indexPath.item]) if indexPath == currentlyPlayingIndex { cell.play() } else { cell.pause() } return cell } func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { if let currentIndex = currentlyPlayingIndex, let currentCell = collectionView.cellForItem(at: currentIndex) as? VideoCollectionViewCell { currentCell.pause() } let cell = collectionView.cellForItem(at: indexPath) as! VideoCollectionViewCell cell.play() currentlyPlayingIndex = indexPath } // Ensuring that the video continues playing even when the cell goes off-screen func scrollViewDidScroll(_ scrollView: UIScrollView) { guard let currentlyPlayingIndex = currentlyPlayingIndex else { return } if let cell = collectionView.cellForItem(at: currentlyPlayingIndex) as? VideoCollectionViewCell { // Ensure the cell keeps playing the video if it's the currently playing index cell.play() } } }
优化方案
1. 彻底清理复用Cell的播放器资源
当前prepareForReuse只暂停并清空playerLayer的player,没有彻底销毁播放器实例,导致资源残留。修改如下:
override func prepareForReuse() { super.prepareForReuse() pause() // 清空播放项,释放资源 player?.replaceCurrentItem(with: nil) player = nil // 移除playerLayer,避免图层残留 playerLayer?.removeFromSuperlayer() playerLayer = nil // 重置按钮状态 playPauseButton.setTitle("Play", for: .normal) }
2. 修复滚动时的无效播放触发
原scrollViewDidScroll每次滚动都调用play(),导致CPU频繁执行播放逻辑。改为判断播放Cell是否在可视区域,不在则暂停并清空当前播放索引:
func scrollViewDidScroll(_ scrollView: UIScrollView) { guard let currentIndex = currentlyPlayingIndex else { return } // 获取可视区域范围 let visibleRect = CGRect(origin: collectionView.contentOffset, size: collectionView.bounds.size) // 获取当前播放Cell的frame guard let cellFrame = collectionView.layoutAttributesForItem(at: currentIndex)?.frame else { currentlyPlayingIndex = nil return } // 如果Cell不在可视区域,暂停播放 if !visibleRect.intersects(cellFrame) { if let cell = collectionView.cellForItem(at: currentIndex) as? VideoCollectionViewCell { cell.pause() } currentlyPlayingIndex = nil } }
3. 正确绑定XIB中的播放按钮
如果按钮在XIB中,直接通过IBOutlet关联,无需代码创建:
- 在XIB中给按钮添加IBOutlet到Cell类:
class VideoCollectionViewCell: UICollectionViewCell { @IBOutlet weak var playPauseButton: UIButton! var player: AVPlayer? var playerLayer: AVPlayerLayer? override func awakeFromNib() { super.awakeFromNib() // 绑定按钮点击事件 playPauseButton.addTarget(self, action: #selector(playPauseTapped), for: .touchUpInside) // 初始化playerLayer并放在按钮下方 playerLayer = AVPlayerLayer() playerLayer?.videoGravity = .resizeAspect contentView.layer.insertSublayer(playerLayer!, below: playPauseButton.layer) } // 其他方法保持不变... }
- 在XIB中设置按钮的约束和样式,无需代码创建布局。
4. 减少cellForItemAt中的无效操作
原代码每次复用Cell都会调用play()或pause(),优化为仅当Cell是当前播放项且在可视区域时才播放:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "VideoCell", for: indexPath) as! VideoCollectionViewCell cell.configure(with: videoURLs[indexPath.item]) let isCurrentPlaying = indexPath == currentlyPlayingIndex let isVisible = collectionView.indexPathsForVisibleItems.contains(indexPath) if isCurrentPlaying && isVisible { cell.play() } else { cell.pause() } return cell }
5. 优化AVPlayer初始化与硬件解码
使用AVAsset和AVPlayerItem初始化播放器,启用硬件加速并调整缓冲时长,减少CPU和内存占用:
func configure(with url: URL) { let asset = AVAsset(url: url) let playerItem = AVPlayerItem(asset: asset) // 缩短缓冲时长,减少内存占用 playerItem.preferredForwardBufferDuration = 1.0 player = AVPlayer(playerItem: playerItem) playerLayer?.player = player }
内容的提问来源于stack exchange,提问作者Ajay Chudasama
相关产品推荐
相关产品推荐

