I'm running into a bit of a strange problem here. One of my NSURLSessions is in charge of getting information for restaurant information that I have stored (restaurant name, restaurant's logo URL, etc), and then the second NSURLSession is in charge of using the restaurant's logo URL to retrieve the specific image and set it for each UITableView's cell.
The problem, however, is that my UITableView does not load anything at all sometimes so the cells are empty, but at other times when I add an extra [_tableView reload]
in the NSURLSessions' completion block in the fetchPosts method, it'll work, but then the cells will stop displaying anything again if I re-run it. Something is definitely wrong. Have a look at my code below:
#import "MainViewController.h"
#import "SWRevealViewController.h"
#import "RestaurantNameViewCell.h"
#import "RestaurantList.h"
@interface MainViewController ()
@end
@implementation MainViewController
- (void)viewDidLoad
{
[super viewDidLoad];
//List of restaurants needed to load home page
_restaurantInformationArray = [[NSMutableArray alloc] init];
self.tableView.dataSource = self;
self.tableView.delegate = self;
//setup for sidebar
SWRevealViewController *revealViewController = self.revealViewController;
if ( revealViewController )
{
[self.sidebarButton setTarget: self.revealViewController];
[self.sidebarButton setAction: @selector( revealToggle: )];
[self.view addGestureRecognizer:self.revealViewController.panGestureRecognizer];
}
//Get list of restaurants and their image URLs
[self fetchPosts];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [_restaurantInformationArray count];
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
RestaurantNameViewCell *cell = (RestaurantNameViewCell *)[_tableView dequeueReusableCellWithIdentifier:@"restaurantName" forIndexPath:indexPath];
RestaurantList *currentRestaurant = [_restaurantInformationArray objectAtIndex:indexPath.row];
cell.restaurantName.text = currentRestaurant.name;
cell.imageAddress = currentRestaurant.imageURL;
cell.restaurantClicked = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapDetected:)];
cell.restaurantClicked.numberOfTapsRequired = 1;
cell.restaurantLogo.userInteractionEnabled = YES;
[cell.restaurantLogo addGestureRecognizer:cell.restaurantClicked];
cell.restaurantLogo.tag = indexPath.row;
//Add restaurant logo image:
NSString *URL = [NSString stringWithFormat:@"http://private.com/images/%@.png",cell.imageAddress];
NSURL *url = [NSURL URLWithString:URL];
NSURLSessionDownloadTask *downloadLogo = [[NSURLSession sharedSession]downloadTaskWithURL:url completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
UIImage *downloadedImage = [UIImage imageWithData:[NSData dataWithContentsOfURL:location]];
cell.restaurantLogo.image = downloadedImage;
}];
[downloadLogo resume];
return cell;
}
-(void)fetchPosts {
NSString *address = @"http://localhost/xampp/restaurants.php";
NSURL *url = [NSURL URLWithString:address];
NSURLSessionDataTask *downloadRestaurants = [[NSURLSession sharedSession]dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSError *someError;
NSArray *restaurantInfo = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&someError];
for(NSDictionary *dict in restaurantInfo) {
RestaurantList *newRestaurant = [[RestaurantList alloc]init];
newRestaurant.name = [dict valueForKey:@"name"];
newRestaurant.imageURL = [dict valueForKey:@"image"];
[_restaurantInformationArray addObject:newRestaurant];
//Refresh table view to make sure the cells have info AFTER the above stuff is done
[_tableView reloadData];
}
}];
[downloadRestaurants resume];
}
@end
It's probably a very stupid mistake that I'm making, but I'm not certain how I should correct this. I'm new to iOS development, so would greatly appreciate some guidance :)
Besides assuming that your network requests aren't erroring (you should at least log if there are network errors), there are threading issues.
Your
NSURLSession
callback probably runs on a background thread. This makes it unsafe to call UIKit (aka -[_tableView reloadData]
). UIKit isn't thread safe. This means invoking any of UIKit's APIs from another thread creates non-deterministic behavior. You'll want to run that piece of code on the main thread:Likewise for fetching the images. It's slightly more complicated because of table view cell reuse which could cause the wrong image to display when scrolling. This is because the same cell instance is used for multiple values in your array as the user scrolls. When any of those callbacks trigger, it'll replace whatever image happens to be in that cell. The generally steps to reproduce this is as follows:
You'll need to make sure the cell is displaying the correct cell before attempting to assign the image to it. If you rather not implement that logic for image fetching in cells, you could use SDWebImage instead. Using SDWebImage's
[UIImageView sd_setImageWithURL:]
is thread safe (it will set the image on the main thread).Side notes:
_restaurantInformationArray
, and not every time in the for loop.