简体   繁体   中英

Displaying download progress in reusable cells

I am trying to display download progress in my collectionview cells. Im currently using the parse progressblock which has an instance of the cell and updates the progress bar.

}, 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
    }
}
})

So this works fine, the problem i now have is if i leave this view controller then come back to see how the downloads are going the instance of the cell has been reused and none of the desired UI elements are visible but the progress is still ticking away in the background.

The only place i can think to re-display the UI elements is in cellForItemAt. The problem then is that the progress doesn't update, it just shows the value at the time the cell was reloaded.

How can i go about reusing the instance of the cell that the progress block is using or cleanly displaying ui elements that continue to update?

Presuming that you're dismissing the old view controller with the collection view and presenting a new one, there are two problems here:

  • You're then trying to update cells in the collection view in the previous view controller; and

  • You're keeping a strong reference to the old view controller that was dismissed.

If this is the case, the goal is to decouple the progress updates from any particular view controller, collection view, or cell. You also probably want to decouple the item/row number, too, in case you insert/remove any cells at any time. The best way to handle this is notifications:

  1. Define a few constants used when defining the notifications:

     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. Have your progressBlock post a notification rather than trying to update the UI directly:

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

    Please note that there are no reference to self here, which keeps the progress block from hanging on to your view controller.

  3. Define some function that you can use to identify which IndexPath corresponds to the identifier for your download. In my simple example, I'm just going to have an array of download identifiers and use that:

     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 } } 

    You'd probably have a download identifier as a property of some Download model object, and use that instead, but hopefully it illustrates the idea: Just have some way to identify the appropriate IndexPath for a given download. (By the way, this decoupling the IndexPath from what it was when you first created the download is important, in case you insert/remove any items from your collection view at any point.)

    Now, you may ask what should you use for the identifier. You might use the URL's absoluteString . You might use some other unique identifier. But I'd discourage you from relying solely on item/row numbers, because those can change (maybe not now, but perhaps later as you make the app more sophisticated, you might be inserting removing items).

  4. Have your collection view's view controller add itself as an observer of this notification, updating the appropriate progress view:

     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) } 

    Please note the [weak self] capture list, to make sure the notification observer doesn't cause a strong reference cycle with the view controller.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM