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 b
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:
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"
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.
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).
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.