如何缓存单元格并重用每个单元格中嵌入了 avplayers 的集合视图中的单元格?

2024-05-11

基本上我想做的是缓存单元格并让视频继续播放。当用户滚动回到单元格时,视频应该只从播放的位置显示。

问题是玩家被移除并且单元格最终出现在随机单元格上,而不是其指定区域。

您需要有两个视频才能正常工作,我从这里下载了视频https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4 https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4我刚刚用两个不同的名称保存了同一个视频两次。

如何复制问题: 点击第一个单元格,然后一直向下滚动,然后向上滚动,您会注意到视频开始出现在各处。我只想让视频出现在正确的位置,而不是出现在其他地方。

这是代码的链接:代码链接 https://github.com/Abstract45/QuestionTest

class ViewController: UIViewController {
    
    private var collectionView: UICollectionView!
    
    private var videosURLs: [String] = [
        "ElephantsDream2", "ElephantsDream", "ElephantsDream", "ElephantsDream", "ElephantsDream", "ElephantsDream", "ElephantsDream", "ElephantsDream", "ElephantsDream", "ElephantsDream", "ElephantsDream", "ElephantsDream", "ElephantsDream", "ElephantsDream",
        "ElephantsDream", "ElephantsDream", "ElephantsDream", "ElephantsDream",
        "ElephantsDream", "ElephantsDream", "ElephantsDream", "ElephantsDream",
        "ElephantsDream", "ElephantsDream", "ElephantsDream", "ElephantsDream"
    ]
    
    var cacheItem = [String: (cell: CustomCell, player: AVPlayer)]()

    override func viewDidLoad() {
        super.viewDidLoad()
            
        setupCollectionView()
    }
    
    private func setupCollectionView() {
        collectionView = UICollectionView(frame: .zero, collectionViewLayout: ColumnFlowLayout())
        view.addSubview(collectionView)

        collectionView.register(CustomCell.self, forCellWithReuseIdentifier: "cell")
        collectionView.dataSource = self
        collectionView.delegate = self
        collectionView.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([
            collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            collectionView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            collectionView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
        ])
    }

}

extension ViewController: UICollectionViewDelegate {
    
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        guard let cell = collectionView.cellForItem(at: indexPath) as? CustomCell else { return }
        let item = videosURLs[indexPath.row]
        let viewModel = PlayerViewModel(fileName: item)
        cell.setupPlayerView(viewModel.player)
        cacheItem[item] = (cell, viewModel.player)
    }
}

extension ViewController: UICollectionViewDataSource {
    
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        videosURLs.count
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let item = videosURLs[indexPath.row]
        if let cachedItem = cacheItem[item], indexPath.row == 0 {
            print(indexPath)
            print(item)
            cachedItem.cell.setUpFromCache(cachedItem.player)
            return cachedItem.cell
        } else {
            guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as? CustomCell else { return UICollectionViewCell() }
            cell.contentView.backgroundColor = .orange
            let url = Bundle.main.url(forResource: item, withExtension: "mp4")
            cell.playerItem = AVPlayerItem(url: url!)
            return cell
        }
    }
}


class CustomCell: UICollectionViewCell {
    private var cancelBag: Set<AnyCancellable> = []
    private(set) var playerView: PlayerView?
    var playerItem: AVPlayerItem?
    
    override var reuseIdentifier: String?{
        "cell"
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        setupViews()
    }
    
    override func prepareForReuse() {
        super.prepareForReuse()
        
        playerView = nil
        playerView?.removeFromSuperview()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    private func setupViews() {
        layer.cornerRadius = 8
        clipsToBounds = true
    }
    
    func setUpFromCache(_ player: AVPlayer) {
        playerView?.player = player
    }
    
    func setupPlayerView(_ player: AVPlayer) {
        if self.playerView == nil {
            self.playerView = PlayerView(player: player, gravity: .aspectFill)
            contentView.addSubview(playerView!)
            
            playerView?.translatesAutoresizingMaskIntoConstraints = false
            
            NSLayoutConstraint.activate([
                playerView!.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
                playerView!.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
                playerView!.topAnchor.constraint(equalTo: contentView.topAnchor),
                playerView!.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
            ])
            
            playerView?.player?.play()
            
            NotificationCenter.default.publisher(for: .AVPlayerItemDidPlayToEndTime).sink { [weak self] notification in
                if let p = notification.object as? AVPlayerItem, p == player.currentItem {
                    self?.playerView?.removeFromSuperview()
                    guard let self = self else { return }
                    NotificationCenter.default.removeObserver(self, name: .AVPlayerItemDidPlayToEndTime, object: nil)
                }
            }.store(in: &cancelBag)
        } else {
            playerView?.player?.pause()
            playerView?.removeFromSuperview()
            playerView = nil
        }
    }
}

在尝试实现你想要的东西时,我会考虑/考虑改变以下一些事情,这可能是我们看到这种奇怪的细胞行为的原因:

  1. Use indexPath.item rather indexPath.row处理集合视图时

  2. In you prepareForReuse,你实际上丢弃了playerView所以当您尝试从缓存中再次恢复播放器时playerView?.player = player - the playerView很可能为零并且需要重新初始化

  3. 您不需要将单元作为一个整体来保存,这可能会起作用,但是我认为我们应该让集合视图完成回收单元的业务。只保留播放器

  4. 我还没有这样做,但是,考虑一下在丢弃单元格时如何处理删除通知观察者,因为您可能会多次订阅通知,这可能会导致问题

  5. 我也没有这样做,但请记住在视频结束时从缓存中删除视频,因为您不希望单元格再做出反应

以下是我所做的一些小更改:

CustomCell

// I made this code into a function since I figured we might reuse it 
private func configurePlayerView(_ player: AVPlayer) {
    self.playerView = PlayerView(player: player, gravity: .aspectFill)
    contentView.addSubview(playerView!)
    
    playerView?.translatesAutoresizingMaskIntoConstraints = false
    
    NSLayoutConstraint.activate([
        playerView!.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
        playerView!.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
        playerView!.topAnchor.constraint(equalTo: contentView.topAnchor),
        playerView!.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
    ])
    
    playerView?.player?.play()
    
    NotificationCenter.default.publisher(for: .AVPlayerItemDidPlayToEndTime).sink { [weak self] notification in
        if let p = notification.object as? AVPlayerItem, p == player.currentItem {
            self?.playerView?.removeFromSuperview()
            guard let self = self else { return }
            NotificationCenter.default.removeObserver(self, name: .AVPlayerItemDidPlayToEndTime, object: nil)
        }
    }.store(in: &cancelBag)
}

func setUpFromCache(_ player: AVPlayer) {
    // Check if the playerView needs to be set up
    if playerView == nil {
        configurePlayerView(player)
    }
    
    playerView?.player = player
}

func setupPlayerView(_ player: AVPlayer) {
    // Replace the code from here and use the function
    if self.playerView == nil {
        configurePlayerView(player)
    } else {
        playerView?.player?.pause()
        playerView?.removeFromSuperview()
        playerView = nil
    }
}

// No big change, this might have no impact, I changed the order
// of operations. You can use your previous code if it makes no difference
// but I added it for completeness
override func prepareForReuse() {
    playerView?.removeFromSuperview()
    playerView = nil
    
    super.prepareForReuse()
}

ViewController

// I use the index path as a whole as the key
// I only store a reference to the player, not the cell
var cacheItem = [IndexPath: AVPlayer]()

// Some changes have to be made to made to support the new
// cache type
func collectionView(_ collectionView: UICollectionView,
                    didSelectItemAt indexPath: IndexPath) {
    guard let cell
            = collectionView.cellForItem(at: indexPath) as? CustomCell else { return }
    let item = videosURLs[indexPath.item]
    let viewModel = PlayerViewModel(fileName: item)
    cell.setupPlayerView(viewModel.player)
    cacheItem[indexPath] = (viewModel.player)
}

// I set up a regular cell here
func collectionView(_ collectionView: UICollectionView,
                    cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    guard let cell
            = collectionView.dequeueReusableCell(withReuseIdentifier: "cell",
                                                 for: indexPath) as? CustomCell else { return UICollectionViewCell() }
    
    let item = videosURLs[indexPath.row]
    cell.contentView.backgroundColor = .orange
    let url = Bundle.main.url(forResource: item, withExtension: "mp4")
    cell.playerItem = AVPlayerItem(url: url!)
    return cell
}

// I manage restoring an already playing cell here
func collectionView(_ collectionView: UICollectionView,
                    willDisplay cell: UICollectionViewCell,
                    forItemAt indexPath: IndexPath)
{
    if let cachedItem = cacheItem[indexPath],
       let cell = cell as? CustomCell
    {
        print("playing")
        print(indexPath)
        print(cachedItem)
        cell.setUpFromCache(cachedItem)
    }
}

经过这些改变,我想你会得到你想要的:

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何缓存单元格并重用每个单元格中嵌入了 avplayers 的集合视图中的单元格? 的相关文章

随机推荐

  • 图像识别后如何在vuforia sdk ImageTarget中显示布局而不是茶壶模型

    如果图像在 qualcomm vuforia sdk 中被识别 我们如何在布局中显示简单的文本 即 Hello 我正在使用 ImageTarget 的 qualcomm vuforia sdk 示例 现在它在识别图像后显示一个茶壶 我是增强
  • Java 7 watchservice获取文件更改偏移量

    我刚刚尝试使用 Java 7 WatchService 来监视文件的更改 这是我敲出的一些代码 WatchService watcher FileSystems getDefault newWatchService Path path Pa
  • 无法分配请求的地址 - 可能的原因?

    我有一个由主服务器和分布式从服务器组成的程序 从属服务器向服务器发送状态更新 如果服务器在固定时间内没有收到特定从属服务器的消息 则会将该从属服务器标记为关闭 这种情况一直在发生 通过检查日志 我发现从站只能向服务器发送一个状态更新 然后永
  • 如何在 MSVS 2012+ 编辑器(和 .NET?)中使用正则表达式替换插入“\”+“n”

    在 Visual Studio 2013 的编辑器中 我理解它与 2012 非常相似 据称使用 NET 正则表达式 我无法获取替换字符串来插入反斜杠和 n 这可能吗 我想插入 n 在第一个 之后 在 C 程序的某些 但不是全部 行上 即使字
  • 如何在pandas中将字符串转换为没有日期的日期时间

    例如issue d数据框中的列是字符串 df issue d Dec 2012 我想将字符串转换为日期时间类型而不是字符串类型 2012 12 怎么做 I use datetime strptime x b Y for x in df is
  • 镀铬中的 SVG 条带

    I am using a svg file to produce a smooth gradient when I noticed some serious banding issues in Google Chrome 20 Even s
  • 如何让我的“点击”功能与 iOS 配合使用

    我有一组充当按钮的 Div 这些按钮有一个简单的 jquery click 函数 该函数适用于除 iOS 之外的所有浏览器 例如 div class button click me div and button click function
  • 如何使对象“a == b”的比较成立? [复制]

    这个问题在这里已经有答案了 这是面试前 JavaScript 在线测试的问题之一 function F var a new F var b new F Q 如何进行比较a b to be true e g console log a b t
  • 在 Android 中通过蓝牙接收音频

    我想创建一个能够接收音频流的 Android 应用程序 我想过使用 A2DP 配置文件 但似乎 Android 不支持 A2DP 接收器 看起来有很多人正在寻找这个问题的解决方案 但是接收普通的比特流 然后在应用程序中将数据转换为音频呢 我
  • 从 1D 列表创建 2D 列表

    我对 Python 有点陌生 我想将一维列表转换为二维列表 给定width and length这个的matrix 说我有一个list 0 1 2 3 我想做一个2 by 2该列表的矩阵 我怎样才能得到matrix 0 1 2 3 widt
  • 如何在 Spring 5 MVC 中将 FilePart 转换为 byte[]

    我有从网络表单接收和上传文件的控制器方法 如何从 FilePart 中提取字节数组并将其保存到数据库 我可以通过使用 FilePart transferTo 将 FilePart 保存到文件中来完成此操作 但这看起来又慢又难看 有更好的方法
  • 如何扩展/架构 ASP.NET MVC 3 授权属性来处理这种情况

    我一直在努力思考这个答案 但找不到如何正确执行此操作的好解决方案 我读过这些文章 http schotime net blog index php 2009 02 17 custom authorization with aspnet mv
  • 如何在Python中使用多处理来加速循环执行

    我有两个清单 列表 A 包含 500 个单词 列表 B 包含 10000 个单词 我正在尝试为列表 A 找到与 B 相关的相似单词 我正在使用 Spacy 的相似函数 我面临的问题是计算需要很长时间 我是多处理使用的新手 因此请求帮助 如何
  • 构建战争时如何包含额外文件?

    我正在尝试添加一个目录 garils app store 对我的战争就像这样BuildConfig groovy grails war resources stagingDir args gt copy file grails app st
  • C# 中的 false 运算符有什么用?

    C 中有两个奇怪的运算符 the 真算子 http msdn microsoft com en us library 6x6y6z4d aspx the 假算子 http msdn microsoft com en us library 6
  • 果园:自定义注册字段

    对于我的 Orchard 项目 我需要用户在注册时提供一些附加信息 说 名字 姓氏 裤子颜色 此信息必须在注册时输入 并且不能推迟到以后 根据客户的订单 我尝试使用配置文件和扩展注册插件来请求这些 但据我所知 这只为我提供了在注册表中显示的
  • 当应用程序终止时,我可以安全地依赖 Threads 中的 IsBackground 吗?

    我正在 GUI 中运行一些后台线程 目前 我正在实现个人线程取消代码 但线程中有 IsBackground 属性 根据 MSDN 它们会自行取消 我知道它将进入 Thread Abort 这很令人讨厌 但是在这个后台线程中没有任何事情需要我
  • 用于神经网络模型预测的数据的缺失值

    我目前有大量数据将用于训练预测神经网络 美国主要机场的千兆字节天气数据 我几乎每天都有数据 但有些机场的数据中存在缺失值 例如 机场在 1995 年之前可能不存在 因此在此之前我没有该特定位置的数据 此外 有些还缺少整年 可能跨度为 199
  • Codeigniter 处理大文件时允许的内存大小耗尽

    我发布此内容是为了防止其他人正在寻找相同的解决方案 因为我刚刚在这个废话上浪费了两天时间 我有一个 cron 作业 每天使用一个非常大的文件更新数据库一次 使用以下代码 if handle fopen dirname FILE uncomp
  • 如何缓存单元格并重用每个单元格中嵌入了 avplayers 的集合视图中的单元格?

    基本上我想做的是缓存单元格并让视频继续播放 当用户滚动回到单元格时 视频应该只从播放的位置显示 问题是玩家被移除并且单元格最终出现在随机单元格上 而不是其指定区域 您需要有两个视频才能正常工作 我从这里下载了视频https commonda