Uneven data loading to UITableViewController from Parse when not using PFQueryTableView - ios

I'm using UITableViewController for displaying data from Parse. It runs perfectly on my Xcode Simulator as i think there's no latency in network. But whenever i'm uploading the code to AppStore for Testing. The very first time i run the app it has to load a couple of restaurant's from Parse and display in UITableViewController. Upon clicking a row the first rows data is being loaded into the 3rd row and 4th row data loading in 6th row data irregularly. Why is the data being loaded very unevenly ? Here's my
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
NSString *cellIdentifier = #"restaurantIdentifier";
CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
cell = [[CustomCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
PFObject *tempObject = [self.objectArray objectAtIndex:indexPath.row];
PFFile *imageFile = [tempObject objectForKey:#"RestaurantIcon"];
PFImageView *imageView = [[PFImageView alloc] init];
imageView.file = imageFile;
[imageView loadInBackground:^(UIImage *img,NSError *error){
if(!error){
cell.imageCell.image = imageView.image;
}
}];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.imageView.contentMode = UIViewContentModeScaleAspectFit;
cell.imageView.layer.masksToBounds = YES;
cell.imageView.layer.cornerRadius = 4;
cell.imageView.frame = self.view.bounds;
cell.cellLabel.text = [tempObject objectForKey:#"RestaurantName"];
[self.hotelNamesArray addObject:[tempObject objectForKey:#"RestaurantName"]];
cell.cellLabel.lineBreakMode = NSLineBreakByWordWrapping;
return cell;
}
- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
_restaurantName = [self.hotelNamesArray objectAtIndex:indexPath.row];
self.restaurantMenuNameArray = [[NSMutableArray alloc] init];
PFQuery *query = [PFQuery queryWithClassName:[self.hotelNamesArray objectAtIndex:indexPath.row]];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
for (PFObject *obj in objects) {
if (![_restaurantMenuNameArray containsObject:[obj objectForKey:#"RestaurantMenuName"]]) {
NSLog(#"restaurantmenunames are %#",[obj objectForKey:#"RestaurantMenuName"]);
if ([obj objectForKey:#"RestaurantMenuName"] ==nil) {
[self performSegueWithIdentifier:#"restaurantDetail" sender:self];
return;
}else {
[_restaurantMenuNameArray addObject: [obj objectForKey:#"RestaurantMenuName"]];
}
}
}
}else {
// Log details of the failure
NSLog(#"Error: %# %#", error, [error userInfo]);
}
[self.tableView reloadData];
NSLog(#"restaurantMenuNames is %#",_restaurantMenuNameArray);
[self performSegueWithIdentifier:#"restaurantDetail" sender:self];
}];
}
Thanks in advance.

If you mean the images get in the wrong cell, you have to consider that cells are recycled when you scroll, and that if the image loading takes a bit too long, you may get the result after the cell has been reused.
You need to check that the cell is still for the item/row you want (you could store the row in the cell's tag and check it before setting the image in the completion handler, for instance).
If it's other data that is mixed up, then you'll need to show us the code that loads that data.

Related

UItableView deplicated cell when scrolling

Sorry for posting this question again but I've looked into many answers and neither of them was helpfull to solve my issue.
So this my code :
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *cellIdentifier = #"radioCell";
RadioTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
cell = [[RadioTableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:cellIdentifier];
}
[self configureCommentCell:cell atIndexPath:indexPath];
return cell;
}
when I scroll down my cell get mixed up and some of data are repeated, so I've tried this :
static NSString *CellIdentifier = #"memberCell";
RadioCell *cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[RadioTableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier];
}
and this :
RadioTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:nil];
if (cell == nil) {
cell = [[RadioTableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:nil];
}
But it didn't fixed my issue and I get white empty cells ? please how to fix this issue ?
Update
- (void)configureCommentCell:(RadioTableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
NSDictionary *object;
if ([_dataArray[indexPath.section] isKindOfClass:[NSArray class]])
object = [_dataArray[indexPath.section] objectAtIndex:indexPath.row];
else
object = [[_dataArray[indexPath.section] valueForKey:#"radioList"] objectAtIndex:indexPath.row];
if (object[#"jsonUrl"]) {
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:object[#"jsonUrl"] parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
//NSDictionary *tempObject = (NSDictionary *) responseObject;
if (![[responseObject objectForKey:#"type"] isEqualToString:#"error"]) {
NSDictionary *tempObject = [responseObject[#"data"] objectAtIndex:0];
cell.playingNow.text = tempObject[#"song"];
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
}
cell.name.text = [NSString stringWithFormat:#" %#", object[#"title"]];
if (object[#"logoUrl"])
[cell.logo setImageWithURL:[NSURL URLWithString:object[#"logoUrl"]]];
}
I see that your problem is that you are fetching the data of you cells inside configureCommentCell that's called inside cellForRowAtIndexPath. which is wrong, because it too late to fetch data inside cellForRowAtIndexPath, in this delegate method you should return the cell.
this line may be called before retrieving the data from server :
cell.name.text = [NSString stringWithFormat:#" %#", object[#"title"]];
Instead you should:
Fetch the data inside a separate method for example fetchData
when the data is downloaded inside the completion block of AFNetworking method, store the data inside an NSArray called for example myDataArray still inside the completion block call [self.tableView reloadData];
In viewDidLoad method just call your method fetchData
And your cellForRowAtIndexPath should looks like this:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// hey please give me the cell to display ... harry up please
// please harry up ! oh my god you are fetching data from server
// while I am asking for the cell !
// ok I don't care do what you want
// I will return an empty cell anyway
// and guess what I will not take in consideration
// the retried data because it's inside a block
// which is called asynchronously
static NSString *cellIdentifier = #"radioCell";
RadioTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; if (cell == nil) {
cell = [[RadioTableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:cellIdentifier]; }
// now before return the cell you need to update the content of cell
// maybe you have an array of items and you should update the label
// for example here and then return the cell
cell.usernameLabel = self.myDataArray[indexPath.row]; // example
return cell;
}
Well the TableView is reusing the cells, and you add the image every time a cell is displaid. Thus when reusing the cell you add an other image, but there already is an image.
You will have to reuse the image view, and only add the image if you create the cell.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *cellIdentifer = #"cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifer];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifer]autorelease];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(20,0,30,44)];
imageView.tag = 1001;
[cell addSubview:imageView];
[imageView release], imageView= nil;
}
TabBarTestAppDelegate *delegate = (TabBarTestAppDelegate *)[[UIApplication sharedApplication] delegate];
NSArray *local = delegate.myData;
// ok, it's horrible, don't look at it :-)
cell.textLabel.text = [NSString stringWithFormat:#"%#%#", #" " ,[local objectAtIndex:indexPath.row]];
//
NSString* name = nil;;
if (indexPath.row == 0) {
name = #"topicon";
}
else if (indexPath.row + 1 == [local count]) {
name = #"bottomicon";
}
else {
name = #"innericon";
}
UIImageView *imageView = (UIImageView *)[cell viewWithTag:1001];
imageView.image = [UIImage imageWithContentsOfFile: [[NSBundle mainBundle] pathForResource:name ofType:#"png"]];
return cell;
}

UITableView weird behavior when scrolling

I have created a UITableView which contains cells that display Users. Each cell is added within this method -tableView:cellForRowAtIndexPath:. And each cell has content linked to the specific user, like an UIImageView and UILabel.
The UITableView works properly as long as there is no more than 9-10 cells displaying. But when the number of cells become higher, so the user has to scroll down to view them all, that's when the odd behavior begins. Content from the first, second, third and so on, is added to cell number eleven, twelve, thirteen and so on. And when the user then scroll up, the content that is supposed to be on number 11, 12, 13 is now in the first, second and third cell...
I hope someone understands my problem, and know what is wrong here..
Here is the code I user to add cells.. Ignore the parse stuff though, I dont think it is relevant
- (UITableViewCell *)tableView:(UITableView *)tableview cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *simpleTableIdentifier = #"SimpleTableCell";
UITableViewCell *cell = [tableview dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier];
if (tableview == commentViewTableView) {
//Ignore this
} else if (tableview == tableView) {
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(5, 5, 34, 34)];
imageView.contentMode = UIViewContentModeScaleAspectFill;
imageView.clipsToBounds = YES;
[cell addSubview:imageView];
UILabel *usernameLabel = [[UILabel alloc] initWithFrame:CGRectMake(44, 0, 160, 44)];
usernameLabel.textAlignment = NSTextAlignmentLeft;
usernameLabel.font = [UIFont systemFontOfSize:17];
usernameLabel.backgroundColor = [UIColor clearColor];
[cell addSubview:usernameLabel];
UIImageView *hitImageView = [[UIImageView alloc] initWithFrame:CGRectMake(245, 9.5, 25, 25)];
hitImageView.contentMode = UIViewContentModeScaleAspectFill;
hitImageView.clipsToBounds = YES;
hitImageView.image = [UIImage imageNamed:#"hit.png"];
[cell addSubview:hitImageView];
NSString *key = //Code to retrieve userKey
PFQuery *query = [PFUser query];
[query whereKey:#"objectId" equalTo:key];
[query getFirstObjectInBackgroundWithBlock:^(PFObject *object, NSError *error) {
if (!error) {
[[object objectForKey:#"image1"] getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
if (!error) {
NSString *ageString = [[NSString alloc] initWithFormat:#"%li", (long)age];
imageView.image = [UIImage imageWithData:data];
usernameLabel.text = [NSString stringWithFormat:#"%#, %#", [object objectForKey:#"username"], ageString];
}
}];
}
}];
}
}
return cell;
}
I solved my problem by doing changing the cell identifier to be unique. I don't know if this actually is the way to do it, or if it is good practice, but when I did it solved my problem. So it would be good with some feedback to know if this will cause any other problems I'm might be missing?
NSString *identifier = [NSString stringWithFormat:#"Cell%li", indexPath.row];
UITableViewCell *cell = [tableview dequeueReusableCellWithIdentifier:identifier];
if (cell == nil) {
//My code..
}
Change your code like this:
- (UITableViewCell *)tableView:(UITableView *)tableview cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *simpleTableIdentifier = #"SimpleTableCell";
UITableViewCell *cell = [tableview dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier];
} // CLOSED PARANTHESES HERE!!!!
if (tableview == commentViewTableView) {
//Ignore this
} else if (tableview == tableView) {
// ... rest of your code here
}
}
There are a couple problems with the code. One is that special care must be taken with asynch calls inside the cellForRowAtIndex: datasource method. Another is that the cells are reused, so adding subviews to them each time they come into view will pile subviews upon subview.
Lets start with the asynch operation. #nburk correctly points out the issue, but its an overstatement to say you "can't do it". You could preload everything, but then user must wait for the whole table to be ready before they can see any of it. A good strategy here is lazy load.
Lazy load depends on a place to cache the loaded result. So lets make your datasource array an array of mutable dictionaries that look like this:
#{#"user": aPFUser, #"image": aUIImage };
It makes sense to prefetch the users, otherwise, you don't even know how many you have, so, in viewWillAppear:
// setup your model as #property(strong,nonatomic) NSMutableArray *users;
PFQuery *query = [PFUser query];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
// build the datasource
self.users = [NSMutableArray array];
for (PFUser *user in objects) {
NSMutableDictionary *d = [NSMutableDictionary dictionaryWithDictionary:
#{ #"user": user };
];
}
[self.tableView reloadData];
}
}];
Now, in cellForRowAtIndexPath you do this:
NSMutableDictionary *userDictionary = self.users[indexPath.row];
// in the lazy pattern, if the model is initialized, we're done
// start by assuming the best
imageView.image = userDictionary[#"image"];
// but the image might be nil (it will start out that way) so, load...
PFQuery *query = [PFUser query];
[query whereKey:#"objectId" equalTo:key];
[query getFirstObjectInBackgroundWithBlock:^(PFObject *object, NSError *error) {
if (!error) {
[[object objectForKey:#"image1"] getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
if (!error) {
UIImage *image = [UIImage imageWithData:data];
// this is the important part: this code doesn't run when the rest
// of the method runs. It happens later, when the request completes
// so don't assume anything about the state of the table. Instead
// treat the table like you would in other view controller methods
userDictionary[#"image"] = image;
// don't fool around with cell (it might be reused). Instead
// just tell the table to reload this row
[tableView reloadRowsAtIndexPaths:#[indexPath]
withRowAnimation:UITableViewRowAnimationAutomatic];
}
}];
}
}];
The next time that row scrolls into view, the data from the asynch request will be cached in your user dictionary.
Problem two is simpler: the code builds subviews unconditionally, even if the (reused) cell already has that subview. The answer, again, is that laziness is your friend. Try to get the subview from the cell, and only build it if you must...
// change all of your subview-building code to do this:
UIImageView *imageView = (UIImageView *)[cell viewWithTag:32];
if (!imageView) {
imageView = [[UIImageView alloc] init....
// same code as you had here, adding...
imageView.tag = 32;
}
// and so on for the cell's other subviews. be sure to advance the tag (33, 34, etc)
In sum, the cellForRowAtIndexPath has a few sections.
dequeue the cell
lazy-build subviews as above
as above: access your model and optimistically init the subviews from the model
if part of the model is missing, do an asynch call, update the model,
and reload the cell when done

search function on parse.com overlays search results on original tableview

I'm pretty new at this and having a go using Parse.com as the backend server. I'm building a database of vegetables, and want to perform a search on the list pulled from parse.com. I have it all working, except for one annoying thing...
Now, I'm using storyboards and have created a custom cell which includes a PFImage thumbnail view, a label showing the vegetable, and then another label showing the season for the vegetable.
When the viewcontroller is called, the list populates perfectly and lists the vegetables in alphabetical order. Then I drag the window down to reveal the search bar. I begin typing in a vegetable name, and as I do so the original table data rows begin disappearing (as they should), but the problem is the original table data sticks around. So, for instance, I'll type "carrot", and all the rows disappear except the top row which still holds a thumbnail of an artichoke (and the label "Artichoke" as well). But overlayed on that row is also the word "Carrots", which is another vegetable in the list. If I tap on it, it properly seques to my detail view controller showing carrots. So everything is working properly, but I can't figure out how to make it so the search results aren't being written over the top of the original data.
Here's the code portions:
- (void)viewDidLoad
{
self.tableView.backgroundColor = [UIColor clearColor];
self.tableView.backgroundView=[[UIImageView alloc] initWithImage:
[UIImage imageNamed:#"bg.jpg"]];
[super viewDidLoad];
//add the search bar
self.searchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 44)];
self.tableView.tableHeaderView = self.searchBar;
self.searchController = [[UISearchDisplayController alloc] initWithSearchBar:self.searchBar contentsController:self];
self.searchController.searchResultsDataSource = self;
self.searchController.searchResultsDelegate = self;
self.searchController.delegate = self;
CGPoint offset = CGPointMake(0, self.searchBar.frame.size.height);
self.tableView.contentOffset = offset;
self.searchResults = [NSMutableArray array];
//done adding search bar
}
- (PFQuery *)queryForTable
{
PFQuery *query = [PFQuery queryWithClassName:self.parseClassName];
// If no objects are loaded in memory, we look to the cache first to fill the table
// and then subsequently do a query against the network.
/* if ([self.objects count] == 0) {
query.cachePolicy = kPFCachePolicyCacheThenNetwork;
}*/
[query orderByAscending:#"vegetable"];
return query;
}
// Override to customize the look of a cell representing an object. The default is to display
// a UITableViewCellStyleDefault style cell with the label being the first key in the object.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath object:(PFObject *)object
{
static NSString *simpleTableIdentifier = #"VegetableCell";
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier];
}
// more search stuff
if (tableView == self.tableView) {
cell.backgroundColor = cell.backgroundColor;
}
else if(tableView == self.searchDisplayController.searchResultsTableView) {
PFObject *searchedVeggie = [self.searchResults objectAtIndex:indexPath.row];
cell.textLabel.text = [searchedVeggie objectForKey:#"vegetable"];
cell.backgroundColor = [UIColor whiteColor];
tableView.rowHeight = self.tableView.rowHeight;
}
PFFile *thumbnail = [object objectForKey:#"vegetableImageFile"];
PFImageView *thumbnailImageView = (PFImageView*)[cell viewWithTag:100];
thumbnailImageView.image = [UIImage imageNamed:#"placeholder.jpg"];
thumbnailImageView.file = thumbnail;
[thumbnailImageView loadInBackground];
UILabel *vegetableName = (UILabel*) [cell viewWithTag:101];
vegetableName.text = [object objectForKey:#"vegetable"];
UILabel *vegetableSeason = (UILabel*) [cell viewWithTag:102];
vegetableSeason.text = [object objectForKey:#"vegetableSeason"];
return cell;
}
Lower in the code is my prepareForSeque code and other search methods. I know it's a little ugly with repeated code, but I've been trying all sorts of things to fix my issue and wasn't going to get around to cleaning things up until I figured out the issue. Also, I created a new column on parse.com's data browser called lowerCaseVegetable since the search is case sensitive. So the search is actually performed on that column, but is displayed using the normal "vegetable" column, which has the vegetable name capitalized.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"showVegetableDetail"]) {
if (self.searchDisplayController.active) {
NSLog(#"Search Display Controller");
VeggieDetailViewController *destViewController = segue.destinationViewController;
PFObject *object = [self.searchResults objectAtIndex: self.searchDisplayController.searchResultsTableView.indexPathForSelectedRow.row];
Vegetables *vegetables = [[Vegetables alloc] init];
vegetables.vegetable = [object objectForKey:#"vegetable"];
vegetables.vegetableInfo = [object objectForKey:#"vegetableInfo"];
vegetables.vegetableImageFile = [object objectForKey:#"vegetableImageFile"];
vegetables.vegetableSeason = [object objectForKey:#"vegetableSeason"];
destViewController.vegetables = vegetables;
} else {
NSLog(#"Default Display Controller");
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
VeggieDetailViewController *destViewController = segue.destinationViewController;
PFObject *object = [self.objects objectAtIndex:indexPath.row];
Vegetables *vegetables = [[Vegetables alloc] init];
vegetables.vegetable = [object objectForKey:#"vegetable"];
vegetables.vegetableInfo = [object objectForKey:#"vegetableInfo"];
vegetables.vegetableImageFile = [object objectForKey:#"vegetableImageFile"];
vegetables.vegetableSeason = [object objectForKey:#"vegetableSeason"];
destViewController.vegetables = vegetables;
}
}
}
// other search stuff
-(void)filterResults:(NSString *)searchTerm {
[self.searchResults removeAllObjects];
PFQuery *query = [PFQuery queryWithClassName: #"Vegetables"];
[query whereKeyExists:#"lowerCaseVegetable"];
[query whereKey:#"lowerCaseVegetable" containsString:searchTerm];
NSArray *results = [query findObjects];
[self.searchResults addObjectsFromArray:results];
}
-(BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString {
[self filterResults:searchString];
return YES;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (tableView == self.tableView) {
return self.objects.count;
} else {
return self.searchResults.count;
}
}
-(void)callbackLoadObjectsFromParse:(NSArray *)result error:(NSError *)error {
if (!error) {
[self.searchResults removeAllObjects];
[self.searchResults addObjectsFromArray:result];
[self.searchDisplayController.searchResultsTableView reloadData];
} else {
// NSLog(#”Error: %# %#”, [error userInfo]);
}
}
I have a feeling I'm just making a stupid newbie mistake here, but I've only been at this since May, and specifically fighting this issue the last two weeks. I figured it was about time to ask for help.
I just implemented search in my parse app and I think I see the problem:
In your filterResults method try using findObjectsInBackgroundWithBlock instead of findObjects like this:
[query findObjectsInBackgroundWithBlock:^(NSArray *array, NSError *error){
[self.searchResults addObjectsFromArray:array];
[self.searchDisplayController.searchResultsTableView reloadData];
}];
prepareForSegue:
Vegetables *vegetables = [[Vegetables alloc] init];
vegetables.vegetable = [self.selectedObject objectForKey:#"vegetable"];
vegetables.vegetableInfo = [self.selectedObject objectForKey:#"vegetableInfo"];
vegetables.vegetableImageFile = [self.selectedObject objectForKey:#"vegetableImageFile"];
vegetables.vegetableSeason = [self.selectedObject objectForKey:#"vegetableSeason"];
destViewController.vegetables = vegetables;

UITableView Scroll Lags with Parse Images

I've seen several other threads on this, but none using Parse. We have 3 things going on. First, the TableView that lags when it scrolls. Second, the custom cell with the images and labels. And finally, the ParsePull class that fetches the data. We made a separate class for this to use it for other ViewControllers.
I'm pretty sure the problem is fetching each image every time the cell appears on the screen, but I don't know how to fix it. Below is the related code for each one.
UITableView with the following method:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (!self.eventData.count) {
ParallaxEventCell *loadCell = (ParallaxEventCell *)[tableView dequeueReusableCellWithIdentifier:#"loadingCell" forIndexPath:indexPath];
return loadCell;
} else{
ParallaxEventCell *cell = (ParallaxEventCell *)[tableView dequeueReusableCellWithIdentifier:#"eventCell" forIndexPath:indexPath];
//load an event cell
[cell setUpEvent:[self.eventData objectAtIndex:indexPath.row]];
return cell;
}
}
ParallaxEventCell:
-(void)setUpEvent:(id)event{
self.clipsToBounds = YES;
self.titleLabel.text = [event objectForKey:#"eventTitle"];
self.venueNameLabel.text = [event objectForKey:#"eventVenue"];
self.priceLabel.text = [event objectForKey:#"eventPrice"];
self.customDateLabel.text = [self dateStringFromDate:[event objectForKey:#"eventDate"]];
self.eventURL = [event objectForKey:#"urlString"];
self.geoPoint = [event objectForKey:#"GeoPoint"];
self.descriptionText = [event objectForKey:#"eventDescription"];
[ParsePull picturefromFile:[event objectForKey:#"eventImage"] withCompletion:^(UIImage *returnedImage) {
self.customImageView.clipsToBounds = NO;
self.customImageView.image = returnedImage;
NSLog(#"setUpEvent");
}];
}
- (void)setImage:(UIImage *)image
{
// Store image
self.customImageView.image = image;
// Update padding
[self setImageOffset:self.imageOffset];
}
ParsePull
+(void)picturefromFile:(PFFile *)file withCompletion:(PictureBlock)logo{
NSLog(#"pictureFromFile");
[file getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
if (!error) {
logo([UIImage imageWithData:data]);
}
else{
logo(nil);
}
}];
}

Using AFNetworking to update a UITableViewCell once a download is complete

Within our app we have a free magazine that users can download in PDF format. If they have not downloaded an issue, that UITableViewCell image has a low alpha so that the user can see that it is not downloaded.
If you tap a cell it will start to download using an AFHTTPRequestOperation and once complete you can view the PDF using QuickLook.
The problem I am having is, when the user initiates the download, then scrolls away and then back, the UITableViewCell that they tapped somehow loses reference that it was downloading and therefore doesn't update the UIProgressView or change the alpha to 1.0 when the download is finished. I cannot for the life of me figure out why [[tableView indexPathForCell:cell] isEqual:indexPath] is not equaling:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
PUCViewpointItem *item = [self.items objectAtIndex:indexPath.row];
// Check to see if we are already on the row that is activated
if (indexPath.row == self.selectedIndexPath.row) {
// File Path
NSString *path = [self itemPath:item];
// Should we read the issue
if (item.isDownloaded && path) {
item.downloadPath = path;
[self readIssue:item];
return;
}
// TableView Cell
PUCViewpointTableViewCell *cell = (PUCViewpointTableViewCell *)[tableView cellForRowAtIndexPath:indexPath];
if (!path) {
Utility *utility = [[Utility alloc] init];
NSURLRequest *request = [NSURLRequest requestWithURL:item.url];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
NSString *localPath = [[utility localDirectory] stringByAppendingFormat:#"/%#.pdf", item.name];
operation.outputStream = [NSOutputStream outputStreamToFileAtPath:localPath append:NO];
[operation setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {
float totalProgress = (float)totalBytesRead/totalBytesExpectedToRead;
if ([[tableView indexPathForCell:cell] isEqual:indexPath]) {
cell.progressView.hidden = NO;
cell.progressView.progress = totalProgress;
item.isDownloading = YES;
item.isDownloaded = NO;
item.progress = totalProgress;
}
}];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
if ([[tableView indexPathForCell:cell] isEqual:indexPath]) {
cell.fullImage.alpha = 1.0f;
cell.progressView.hidden = YES;
item.isDownloaded = YES;
item.isDownloading = NO;
}
NSLog(#"%d == %d", [tableView indexPathForCell:cell].row, indexPath.row);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
if ([[tableView indexPathForCell:cell] isEqual:indexPath]) {
cell.progressView.hidden = YES;
item.isDownloading = NO;
item.isDownloaded = NO;
}
}];
[operation start];
}
return;
}
NSIndexPath *oldIndexPath = self.selectedIndexPath;
self.selectedIndexPath = indexPath;
[tableView beginUpdates];
[tableView endUpdates];
// Which way are we scrolling?
UITableViewScrollPosition position;
if (indexPath.row == 0 || (oldIndexPath && oldIndexPath.row < indexPath.row)) {
position = UITableViewScrollPositionTop;
} else {
position = UITableViewScrollPositionBottom;
}
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:position animated:YES];
}
If I start a download, then scroll down in my tableView, my NSLog statement will log something like 3 == 10 which makes no sense.
Any ideas how I can fix this?
I was curious about your case so I wrote a little test project, you can find it here: https://github.com/mrojas/MRTableViewTest
Basically, the way to solve it was:
Put the logic to download an item, in the item class
Make the cell be the delegate of the item, to be notified about progress/completion
When cells are scrolled (reused), setting the item on them is enough. They figure the current status and set themselves to be delegates.
Check the project, try it, and let me know if you have doubts.
I didn't use AFNetworking but instead simulated some task that takes 10 seconds to complete, in 2 seconds interval.
I think you have to store somewhere (like an NSMutableArray instance variable) the indexPath you are downloading. so you can do something like that :
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
if( [indexArray indexOfObject:indexPath] != NSNotFound )
{
// alpha for downloading
}
}
In your AFNetworking completion blocks you should remove this indexPath from you indexArray.
As comments said, cells are not reliable for storing any kind of information as they are reallocated when you scroll
#interface ViewController ()
{
NSMutableArray *_indexArray;
}
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
UITableView *table = [[UITableView alloc] initWithFrame:self.view.frame style:UITableViewStylePlain];
table.delegate = self;
table.dataSource = self;
_indexArray = [#[] mutableCopy];
[self.view addSubview:table];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
{
return 50;
}
// called when you scroll to new cells or when reloadData is called
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
{
UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"default"];
cell.textLabel.textColor = [UIColor blueColor];
// red color if I'm downloading, else blue
if ([_indexArray indexOfObject:indexPath] != NSNotFound) {
cell.textLabel.textColor = [UIColor redColor];
}
cell.textLabel.text = #"cell in the table";
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;
{
[_indexArray addObject:[indexPath copy]];
NSLog(#"downloading...");
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
cell.textLabel.textColor = [UIColor redColor]; // update UI temporary (until scroll)
// HD Image so I can scroll up and down for testing
NSString *res = #"http://res.cloudinary.com/******/image/upload/*****/Motorola_Razr_HD_Cam_Sample_7_ftzrj0.jpg";
// custom class for download
[[PLSApi api] downloadDataAtURL:[NSURL URLWithString:res] withBlock:^(NSData *data) {
// download complete
[_indexArray removeObject:indexPath];
cell.textLabel.textColor = [UIColor blueColor]; // update UI
NSLog(#"finished...");
}];
}

Resources