在可重复使用的单元格中显示下载进度
Posted
技术标签:
【中文标题】在可重复使用的单元格中显示下载进度【英文标题】:Displaying download progress in reusable cells 【发布时间】:2017-02-11 22:22:19 【问题描述】:我正在尝试在我的 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
)
所以这很好,我现在遇到的问题是,如果我离开这个视图控制器然后回来看看下载是如何进行的,单元格的实例已被重用,所需的 UI 元素都不可见,但进度仍在后台滴答作响。
我能想到的唯一重新显示 UI 元素的地方是 cellForItemAt。那么问题是进度不会更新,它只是显示重新加载单元格时的值。
如何重新使用进度块正在使用的单元格实例或干净地显示继续更新的 ui 元素?
【问题讨论】:
让您的进度块更新集合中的另一个变量,例如字典或数组,然后您可以在需要时访问进度值。 我确实尝试过,但是我需要一个计时器来更新用户界面,所以现在我有 2 个进程正在运行以更新 1 个进度视图。然后,尝试使用计时器管理下载队列会变得有点棘手,该计时器不会足够快地使其无效以接收有关更新 ui 的集合视图和单元格的数据。 我会将进度状态对象与单元格相关联,并在每个单元格中运行一个计时器,该计时器根据其分配的状态对象定期刷新其状态,或者按照 rob 的建议使用 NSNotification 【参考方案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"
让您的 progressBlock
发布通知,而不是尝试直接更新 UI:
let percent: Float = ...
let userInfo: [AnyHashable: Any] = [
notificationIdentifierKey: identifier,
notificationPercentKey: percent
]
NotificationCenter.default.post(name: notificationName, object: nil, userInfo: userInfo)
请注意这里没有对self
的引用,这可以防止进度块挂在您的视图控制器上。
定义一些函数,您可以使用它来识别哪个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
模型对象的属性,并使用它来代替,但希望它说明了这个想法:只要有一些方法来识别给定下载的适当 IndexPath
。 (顺便说一句,将 IndexPath
与您首次创建下载时的内容分离很重要,以防您随时从集合视图中插入/删除任何项目。)
现在,您可能会问您应该使用什么作为标识符。您可以使用 URL 的 absoluteString
。您可能会使用其他一些唯一标识符。但我不鼓励您仅仅依赖项目/行号,因为这些可能会改变(也许现在不会,但也许以后当您使应用程序更复杂时,您可能会插入删除项目)。
让您的集合视图的视图控制器将自己添加为该通知的观察者,更新相应的进度视图:
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]
捕获列表,以确保通知观察者不会对视图控制器造成强引用循环。
【讨论】:
这是一个很棒的答案,谢谢。我没有在问题中解释的一个问题是我希望编辑的 collectionView 单元格嵌套在另一个 collectionView 中。所以 self 不持有对 innerCollectionView 的引用,它持有对 outerCollectionView 的引用。使用 collectionView.tag 填充集合很容易,然后索引路径如下:“innerCell.imageCell.image = self.multiPartArray[collectionView.tag][indexPath.item].image”但我现在很难理解如何获得参考collectionView 即 collectionView.tag @Pippo - 我仍然认为你想使用weak
参考。您不希望下载进度更新保留对任何 UIKit 对象的强引用。此外,Apple 也一直不鼓励使用tag
号码。在我看来,最好在模型中使用一些唯一标识符。但你可以为所欲为。以上是关于在可重复使用的单元格中显示下载进度的主要内容,如果未能解决你的问题,请参考以下文章
当 UITableView 单元格中的进度条不可见时,如何更新它们