You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何缩放竖屏视频适配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. 自定义管理(轻量需求)

如果你的需求比较简单,自己写逻辑就能搞定:

  • MyCollectionViewCellprepareForReuse里暂停视频,避免复用后继续播放:
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

火山引擎 最新活动