在可重复使用的单元格中显示下载进度

Displaying download progress in reusable cells

我正在尝试在我的 collectionview 单元格中显示下载进度。我目前正在使用具有单元格实例并更新进度条的解析进度块。

}, progressBlock: { (percent) in
    self.mainQueue.addOperation {

    // set the downloadProgess var to update from cellForItemAt
    // downloadProgress = (Float(percent) / Float(100))

    if let downloadingCell = self.collectionView.cellForItem(at: self.indexPath) as? InnerCollectionCell {
        downloadingCell.progressBar.isHidden = false
        downloadingCell.contentView.bringSubview(toFront: downloadingCell.progressBar)
        downloadingCell.progressBar.setProgress(Float(percent) / Float(100), animated: true)
        downloadingCell.setNeedsDisplay()
        downloadingCell.setNeedsLayout()
        downloadingCell.isUserInteractionEnabled = false
        downloadingCell.spinner.isHidden = true
    }
}
})

所以这工作正常,我现在遇到的问题是,如果我离开这个视图控制器然后回来查看下载情况,单元格的实例已被重用并且 none 所需 UI 元素可见,但进度仍在后台滴答作响。

我唯一能想到重新显示 UI 元素的地方是 cellForItemAt。那么问题是进度不会更新,它只显示重新加载单元格时的值。

我怎样才能重新使用进度块正在使用的单元格实例或干净地显示继续更新的 ui 个元素?

假设您使用 collection 视图关闭旧视图控制器并呈现一个新视图控制器,这里有两个问题:

  • 然后您尝试更新前一个视图控制器中 collection 视图中的单元格;和

  • 您保留了对已关闭的旧视图控制器的强引用。

如果是这种情况,目标是将进度更新与任何特定的视图控制器、collection 视图或单元分离。您可能也想解耦 item/row 数字,以防您随时 insert/remove 任何单元格。处理此问题的最佳方法是通知:

  1. 定义几个在定义通知时使用的常量:

    private let notificationName = Notification.Name(rawValue: "com.domain.app.downloadProgress")
    private let notificationIdentifierKey = "com.domain.app.download.identifier"
    private let notificationPercentKey = "com.domain.app.download.percent"
    
  2. 让您的 progressBlock post 收到通知,而不是尝试直接更新 UI:

    let percent: Float = ...
    let userInfo: [AnyHashable: Any] = [
        notificationIdentifierKey: identifier,
        notificationPercentKey: percent
    ]
    NotificationCenter.default.post(name: notificationName, object: nil, userInfo: userInfo)
    

    请注意,这里没有对 self 的引用,这可以防止进度块挂在您的视图控制器上。

  3. 定义一些函数,您可以使用它来识别哪个 IndexPath 对应于您的下载标识符。在我的简单示例中,我只需要一个下载标识符数组并使用它:

    var downloadIdentifiers = [String]()
    
    private func indexPath(for identifier: String) -> IndexPath? {
        if let item = downloadIdentifiers.index(of: identifier) {
            return IndexPath(item: item, section: 0)
        } else {
            return nil
        }
    }
    

    您可能有一个下载标识符作为某些 Download 模型 object 的 属性,并使用它来代替,但希望它能说明这个想法:有一些方法为给定的下载确定合适的 IndexPath。 (顺便说一句,将 IndexPath 与您首次创建下载时的内容分离很重要,以防您 insert/remove 任何时候 collection 视图中的任何项目。)

    现在,您可能会问应该使用什么作为标识符。您可以使用 URL 的 absoluteString。您可能会使用其他一些唯一标识符。但我不鼓励你仅仅依赖 item/row 数字,因为这些数字可能会改变(也许现在不会,但以后当你使应用程序变得更复杂时,你可能会插入删除项目)。

  4. 让您的 collection 视图的视图控制器将自己添加为该通知的观察者,更新相应的进度视图:

    private var observer: NSObjectProtocol!
    
    override func viewDidLoad() {
        super.viewDidLoad()
    
        observer = NotificationCenter.default.addObserver(forName: notificationName, object: nil, queue: .main) { [weak self] notification in
            if let identifier = notification.userInfo?[notificationIdentifierKey] as? String,
                let percent = notification.userInfo?[notificationPercentKey] as? Float,
                let indexPath = self?.indexPath(for: identifier),
                let cell = self?.collectionView?.cellForItem(at: indexPath) as? InnerCollectionCell {
                    cell.progressView.setProgress(percent, animated: true)
            }
        }
    
        ...
    }
    
    deinit {
        NotificationCenter.default.removeObserver(observer)
    }
    

    请注意 [weak self] 捕获列表,以确保通知观察者不会导致与视图控制器的强引用循环。