Reputation: 1826
I currently have a set up where tapping a cell causes an activity indicator to come up on the cell while a network process is being done. Once it finishes, the cell automatically updates its UI.
The problem is, while this is going on, if a user scrolls the cell off the screen it currently gets reused and when you scroll back the cell is either in the pre-tapped UI, or the post-tapped UI (depending on whether process completed while user was scrolled away).
This makes it seem like the background action being done just stops randomly to a users perspective, but it is still happening, just the UI gets refreshed.
So I'm struggling with how to either temporarily prevent the reuse of the cell, or otherwise is there any way I can maintain a consistent loading indicator on a cell (and only on that cell) even after reuse?
EDIT: The idea of saving the indexPath
with some data to display from instead is a step in the right direction, but the ordering is likely, but not guaranteed to remain the same.
So I guess the problem becomes: I need to find something unique to identify a given cell with when I get the delegate call that the process is done. I attempted to use the label's text since it is "unique" (99% of the time...) but this only is guaranteed to work unless I also pass the "unique" identifier (the label, which isn't even guaranteed to be unique) on a fun roller coaster ride through my support classes just so it can be used at the VERY end.
1) Attempt: Retaining the cell by placing it in a NSMutableArray property while it was loading, and using a delegate method to remove it once it finished i.e.:
[self.loadingCells addObject:cell];
Result: No Effect
2) Attempt: Adding a property to the UITableViewCell (I subclassed) called isLoading
.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
...
if (cell.isLoading) {
[cell.activityView startAnimating];
}
}
Result: Loading indicator on correct cell, but also others when off screen.
Worked decently, but I ran into the issue of other random cells now mysteriously looking like they're loading because the original cell got reused to make them.
3) Attempt: Same as attempt #1, except using the (unique) cell's label's text:
[self.loadingCells addObject:cell.label.text];
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
...
if ([self.loadingCells containsObject:cell.label.text]) {
[cell.activityView startAnimating];
}
}
// delegate method
- (void)finishedLoading:(MyCell *)cell {
[cell.activityView stopAnimating];
[self.loadingCells removeObject:cell.label.text];
}
Result: Loading indicator on correct cell only, but delegate method tries to remove wrong label's text if cell not on screen when process finishes, resulting in cell looking like its perpetually loading.
4) I then proceeded to ALSO pass an NSString on the delegate method and attempt to compare them and remove the correct string... This gave unreliable results as well. I presume that this is because the cell that gets passed along to the classes that handle the network calls may doesn't remain static and such if I happen to not be showing the cell when I make the call to extract the label's text it'll be incorrect.
So I COULD make the NSString right away and pass it along through the wonderful web of calls I make in different classes but this is just ridiculous and I was already disliking this solution...
Upvotes: 2
Views: 64
Reputation: 14656
You shouldn't store this type of data on a cell and I promise you definitely don't want to keep the cells from being reused. That's is one of the most powerful features behind the UITableView and what keeps it moving so fast.
The best way to handle this would be to have an array, or other type of data store that holds onto all the data that is used to "configure" your cells. Essentially, have an array that you can check is cellForItemAtIndexPath
to see if the cell needs the indicator or not. Then configure the cell accordingly based on this data. This way you don't actually need to keep state in the cell.
It's hard to layout exactly how this would be done, but here is a very contrived example of how one might check to see if a cell has already been loaded using a map. If the cell isn't in the map, they could add it with a default value and carry on processing as usual.
//: Playground - noun: a place where people can play
import UIKit
let index1 = NSIndexPath(forRow: 0, inSection: 0)
let index2 = NSIndexPath(forRow: 1, inSection: 0)
let index3 = NSIndexPath(forRow: 2, inSection: 0)
let index4 = NSIndexPath(forRow: 3, inSection: 0)
var cellIsLoadingMap = [NSIndexPath: Bool]()
cellIsLoadingMap[index1] = true
cellIsLoadingMap[index2] = true
cellIsLoadingMap[index3] = false
cellIsLoadingMap[index4] = false
let index5 = NSIndexPath(forRow: 4, inSection: 0)
if let isLoading = cellIsLoadingMap[index5] {
print(isLoading)
} else {
cellIsLoadingMap[index5] = false
}
Upvotes: 3