如何缩放竖屏视频适配UICollectionView单元格UIView及实现内联播放?
解决UICollectionView信息流视频适配与内联播放问题
嘿,我来帮你搞定这几个信息流视频开发里的常见问题,一步步来哈 😊
一、让视频完美适配PlayerView
你的PlayerViewClass基于AVPlayerLayer实现,视频显示的适配核心靠它的videoGravity属性——默认值是resizeAspect(保持比例完整显示,所以竖屏视频在横屏容器里会留边)。根据你的需求,有两种方案可选:
1. 填满容器(裁剪超出部分)
如果希望视频完全铺满playerView、不留任何白边,直接设置resizeAspectFill即可:
// 在cellForItemAt方法里添加这行代码 cell.playerView.playerLayer.videoGravity = .resizeAspectFill
这个选项会保持视频比例,同时填满整个容器,超出的部分会被裁剪掉,适合追求全屏视觉效果的场景。
2. 完整显示视频(调整容器适配视频)
如果想完整展示视频内容、不想裁剪,那就要让playerView的尺寸匹配视频的比例——这部分和下面的单元格尺寸适配是联动的,我们接着往下看。
二、让CollectionViewCell尺寸适配视频比例
要实现单元格尺寸随视频比例动态变化,你需要先获取视频的真实宽高比(注意处理视频旋转的情况),再在sizeForItemAt代理方法里计算单元格大小。
1. 封装视频比例获取方法
先写个工具方法,用来获取视频的真实宽高比(处理旋转后的实际显示比例):
func getVideoAspectRatio(from url: URL) -> CGFloat { let asset = AVAsset(url: url) guard let videoTrack = asset.tracks(withMediaType: .video).first else { return 16/9 // 默认返回16:9比例 } // 处理视频旋转,比如竖屏视频的naturalSize是宽<高,但实际显示是竖屏,需要修正 let naturalSize = videoTrack.naturalSize let transform = videoTrack.preferredTransform let correctedSize = naturalSize.applying(transform) return abs(correctedSize.width / correctedSize.height) }
2. 预加载视频比例(避免滚动卡顿)
直接在sizeForItemAt里同步获取视频比例会导致滚动卡顿,建议提前在数据源模型里预加载并缓存比例:
// 假设你的Post数据模型 class Post { var fullURL: URL var videoAspectRatio: CGFloat? // 其他属性... func loadVideoAspectRatio(completion: @escaping () -> Void) { guard videoAspectRatio == nil else { completion() return } let asset = AVAsset(url: fullURL) asset.loadValuesAsynchronously(forKeys: ["tracks"]) { [weak self] in DispatchQueue.main.async { guard let self = self else { return } self.videoAspectRatio = self.getVideoAspectRatio(from: self.fullURL) completion() } } } private func getVideoAspectRatio(from url: URL) -> CGFloat { let asset = AVAsset(url: url) guard let videoTrack = asset.tracks(withMediaType: .video).first else { return 16/9 } let naturalSize = videoTrack.naturalSize let transform = videoTrack.preferredTransform let correctedSize = naturalSize.applying(transform) return abs(correctedSize.width / correctedSize.height) } }
然后在FeedViewController里提前加载比例,加载完成后刷新对应单元格:
override func viewDidLoad() { super.viewDidLoad() // 假设posts是你的数据源数组 posts.enumerated().forEach { index, post in post.loadVideoAspectRatio { [weak self] in self?.collectionView.reloadItems(at: [IndexPath(item: index, section: 0)]) } } }
3. 实现sizeForItemAt方法
现在可以在代理方法里根据缓存的比例计算单元格大小了:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { let post = posts[indexPath.item] let ratio = post.videoAspectRatio ?? 16/9 let cellWidth = collectionView.bounds.width - 20 // 左右各留10pt间距,根据你的UI调整 let cellHeight = cellWidth / ratio return CGSize(width: cellWidth, height: cellHeight) }
三、内联播放+避免多视频同时播放的方案
1. 自定义管理(轻量需求)
如果你的需求比较简单,自己写逻辑就能搞定:
- 在
MyCollectionViewCell的prepareForReuse里暂停视频,避免复用后继续播放:
class MyCollectionViewCell: UICollectionViewCell { @IBOutlet weak var playerView: PlayerViewClass! override func awakeFromNib() { super.awakeFromNib() } override func prepareForReuse() { super.prepareForReuse() playerView.player?.pause() playerView.player = nil // 重置player,避免复用旧实例 } }
- 在FeedViewController里监听滚动,管理当前播放的视频:
var currentPlayingIndex: IndexPath? func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { updatePlayingVideo() } func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { if !decelerate { updatePlayingVideo() } } private func updatePlayingVideo() { // 暂停之前播放的视频 if let index = currentPlayingIndex, let cell = collectionView.cellForItem(at: index) as? MyCollectionViewCell { cell.playerView.player?.pause() } // 找到当前可见的第一个单元格(可根据需求调整为中间或其他位置) let visibleIndices = collectionView.indexPathsForVisibleItems.sorted() guard let firstVisibleIndex = visibleIndices.first else { currentPlayingIndex = nil return } // 播放当前视频 if let cell = collectionView.cellForItem(at: firstVisibleIndex) as? MyCollectionViewCell { cell.playerView.player?.play() currentPlayingIndex = firstVisibleIndex } else { currentPlayingIndex = nil } }
2. 现成框架推荐(复杂需求)
如果需要更完善的功能(比如自动静音播放、视频缓存、手势控制等),推荐这些成熟的开源框架:
- PlayerKit:轻量级的AVPlayer封装,支持内联播放、自动播放、多视频管理,API简洁易上手。
- SwiftVideoPlayer:纯Swift编写的播放器,支持内联播放、循环播放、进度控制,适配各种屏幕尺寸。
- KVOController:配合AVPlayer使用,可简化播放状态的监听,避免手动管理KVO的繁琐。
内容的提问来源于stack exchange,提问作者Sergio Charles




