UITableView is only populated after I scroll - ios

I have 2 table views in 2 view controllers. When i select a cell from first screen i want to load the second table , but my problem is : the table is empty and is populating after i scroll.
This is how i populate my table :
When cell is selected :
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
NSURL *url = [NSURL URLWithString:#"http://privatereisen.com/"];
ProgViewController *cd = [self.storyboard instantiateViewControllerWithIdentifier:#"progidentifview"];
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:url];
NSString *path=[NSString stringWithFormat :#"dok/TV/pays/italie/json/chaine%d.json",indexPath.row];
[httpClient postPath:path parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject)
{resultofData = [NSJSONSerialization JSONObjectWithData:responseObject options:kNilOptions error:nil];
NSString *valuKey=[NSString stringWithFormat :#"chaine%d",indexPath.row];
NSArray *testArray =[resultofData valueForKey:valuKey];
cd.categ=[[NSArray alloc]initWithArray:testArray];
cd.categArray=[[NSArray alloc]initWithArray:self.categoryArray];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[[[UIAlertView alloc]initWithTitle:#":( " message:#"Pas d'internet !" delegate:nil cancelButtonTitle:#"Ok" otherButtonTitles:nil]show];
// NSLog(#"[HTTPClient Error]: %#", error.localizedDescription);
}];
[self.navigationController pushViewController:cd animated:YES];
[tableView cellForRowAtIndexPath:indexPath].selected = NO;
}
and in ProgViewController :
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
cprogCell *cell = [tableView dequeueReusableCellWithIdentifier:#"identifproecell"];
cell.heure.text = [[_categ objectAtIndex:indexPath.row]objectForKey:#"horaire_programme"];
cell.type.text = #"";
cell.desc.text = [[_categ objectAtIndex:indexPath.row]objectForKey:#"nom_programme"];
return cell;
}

You need to reload the UITableView once your asynchronous request has completed. Currently you push the UIViewController onto the stack and then datasource for it gets set later on, so the cell's only get loaded once you scroll.
To fix this, after you set the cd.categ and cd.categArray, make sure you either call [cd.tableView reloadData], or in the setters for categ or categArray call [self.tableView reloadData]

Too late, but I think you are handling the data to be displayed in the wrong place. For a period of time the user experience is to see a blank screen with no data.
It would be better to move all the data into the controller being pushed. So in your select, create the controller and set the data to be fetched as a property. In the ProgViewController, when inside viewDidAppear start the fetch and activate an activity indicator showing that it is busy. On success or failure stop the activity indicator. On success, call self.tableView.reloadData. On failure, raise the alert and pop the controller.
This way everything is being encapsulated and handled in the right place with the UI updating itself as its internal data state changes.
If you do the fetch from within the initial controller, then you should show its busy and only push the new controller once you have a result for it to display IMHO.

Related

UITableView not loading data on app load // Unable to successfully query related photos for UITableViewCells

I have two issues here.
1) The data in my UITableView does not load when I first open up its ViewController. The proper data does end up appearing, but but only after I add more data to it on another ViewController, and then come back to the TableViewController. Even when doing that, the UITableView is always one item behind, meaning that when I first load the app I will see nothing in my TableView, then if I add an item, for example called "hat" in a second ViewController, I will come back to the TableViewController and see all the items I'd added previously, but I will have to add another item, for example called "chair" to my table in order to see "hat" in my table.
2) While I am able to have users successfully add items to my Parse database and then view the items they've added in the UITableView (albeit with the roadblock addressed in issue 1 above), I am unable to successfully populate the UIImage in each cell in the TableView with the photo file of each cell's corresponding item. The photos are being successfully saved to Parse, so it is definitely a problem querying for them properly, or keying into the exact place they are stored, or simply configuring my subclassed TableViewCell to display them properly.
ItemsTableViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[self.navigationItem setHidesBackButton:YES];
}
-(void)viewWillAppear:(BOOL)animated
{
PFQuery *query = [PFQuery queryWithClassName:#"giveItem"];
[query whereKey:#"giver" equalTo:[PFUser currentUser]];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
self.myGiveItems = [[NSMutableArray alloc]init];
for (PFObject *object in objects) {
PFGiveItem *newGiveItem = [[PFGiveItem alloc]init];
newGiveItem.giveItemName = object[#"giveItemTitle"];
// return photo files for each of the objecs
PFFile *giveItemImageFile = object[#"imageFile"];
[giveItemImageFile getDataInBackgroundWithBlock:^(NSData *imageData, NSError *error) {
if (!error) {
UIImage *giveItemImageForCell = [UIImage imageWithData:imageData];
newGiveItem.giveItemImage = giveItemImageForCell;
};
}];
[self.myGiveItems addObject:newGiveItem];
}
} else {
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
[self.tableView reloadData];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.myGiveItems.count;
}
-(void)tableView:(UITableView *)tableView willDisplayFooterView:(UIView *)view forSection:(NSInteger)section
{
tableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectZero];
}
- (JFGiveItemCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellIdentifier = #"Cell";
JFGiveItemCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil){
cell = [[JFGiveItemCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier];
}
PFGiveItem *giveItem = self.myGiveItems[indexPath.row];
cell.giveItemLabel.text = giveItem.giveItemName;
cell.giveItemImageView.image = giveItem.giveItemImage;
return cell;
}
My Parse backend is structured as follows
I will be very receptive to any guidance on how to more properly structure my database to be less redundant and more powerful.
Class 1 "Item" with rows:
"itemTitle," a user-input string from the AddItem ViewController
"owner," the user who adds the item (and photo)
"itemPhoto," a pointer to an object of Class 2, "ItemPhoto"
Class 1 "Item" with rows:
"imageOwner," the user who adds the item (and photo)
"imageName," a string the same as the itemTitle from Class 1
"imageFile," a File uploaded by the user
All of these items are saved successfully, but for your better understanding, here is the code I use to do this.
AddItemViewController
-(BOOL)textFieldShouldReturn:(UITextField *)textField
{
NSString *nameForGiveItem = self.giveItemTitleTextField.text;
NSData *giveItemImageData = UIImagePNGRepresentation(self.giveItemImage);
PFFile *giveItemImageFile = [PFFile fileWithName:nameForGiveItem data:giveItemImageData];
PFObject *giveItemPhoto = [PFObject objectWithClassName:#"giveItemPhoto"];
giveItemPhoto[#"imageOwner"] = [PFUser currentUser];
giveItemPhoto[#"imageName"] = nameForGiveItem;
giveItemPhoto[#"imageFile"] = giveItemImageFile;
[giveItemPhoto saveInBackground];
PFObject *giveItem = [PFObject objectWithClassName:#"giveItem"];
giveItem[#"giveItemTitle"] = self.giveItemTitleTextField.text;
giveItem[#"giver"] = [PFUser currentUser];
[giveItem setObject:giveItemPhoto forKey:#"giveItemPhoto"];
[giveItem saveInBackground];
[self.navigationController popToViewController:[self.navigationController.viewControllers objectAtIndex:1] animated:YES];
return YES;
}
For 1)
In viewWillAppear you call [self.tableView reloadData]; outside of the block. That means you actually call it before the block has finished executing - meaning before the data was loaded.
Move it to after the for block but make sure it is executed on the main thread - otherwise it will not influence the appearance of the ui - meangin the table will not be acualized either.
For 2)
The solution for 1) should fix 2) as well. You load your images asynchronously, which is perfect. But when that is finished you do not reload the table. When you manage to get it reloaded once all the data has been loaded then the cell images will be refreshed as well.
3)
You may want to add any type of view/spinner/progress indicator or just a regular lable that indicates to the user that some data is still loading ...

Loading Facebook data into a table view

I am stuck on the PROPER way to get all a user's Facebook friends and load it into a table view (I would simply use FBFriendPickerViewController but its ugly and it doesn't appear that you can fix that)
All I want is the profile picture in the cell imageView and name in the cell textLabel, simple as that.
Doing it with NSURL/NSData doesn't crop the images, Id much rather use FBProfilePictureView so please try to use that in any solutions. Thanks!
Get data from Facebook
-(void)facebookOpenSession {
if (FBSession.activeSession.isOpen) {
[[FBRequest requestForMyFriends] startWithCompletionHandler:^(FBRequestConnection *connection, id result, NSError *error) {
if (error) {
// handle error
} else {
self.facebookFriends = [NSArray arrayWithArray:[result objectForKey:#"data"]];
[[NSNotificationCenter defaultCenter] postNotificationName:#"facebookRequestForMyFriends" object:self];
}
}];
}
}
Configure data into cell
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
// Configure the cell...
NSDictionary<FBGraphUser> *friend = [self.facebookFriends objectAtIndex:indexPath.row];
cell.textLabel.text = friend.name;
NSURL *profilePictureUrl = [NSURL URLWithString:[NSString stringWithFormat:#"http://graph.facebook.com/%#/picture?type=normal", friend.id]];
NSData *profilePictureData = [NSData dataWithContentsOfURL:profilePictureUrl];
cell.imageView.image = [UIImage imageWithData:profilePictureData];
return cell;
}
How do people learn how to make all these things with the Facebook SDK, I spent almost 4 hours trying to do what I described above and I feel like I am completely missing something.
There are several open source libraries that make the task of downloading an image easy. My favorite is AFNetworking, but there's also SDWebImage. Using one of these categories, your task becomes as simple as:
[myImageView setImageURL:"http://test.com/test.jpg"];
Even if you don't want to use these libraries, don't do it the way you're currently attempting it (i.e. [NSData dataWithContentsOfURL:profilePictureUrl]). I believe that is a synchronous operation that will block your UI. Use code from one of the 2 links above, or make your own UIImageView category.

UITableView - AFNetworking operation in background freezes scrolling

I have a sample application with a UITableViewController.
As in the facebook newsfeed, the app is supposed to download a first time X news, and then fetch news progressively as the user scroll.
Here is my implementation :
-(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath{
if (indexPath.row == self.newsList.count-PADDLE_BEFORE_FETCHING && !cantFetchMore)
if (!fetching){
fetching = YES;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
[self fetchNews];
});
}
}
(the idea is to start fetching additional news when we reach the N-PADDLE_BEFORE_FETCHING cell, only if we can still fetch some - see below- and if fetching is still not currently running)
and then the implementation of fetchNews :
-(void)fetchNews{
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *url = [NSString stringWithFormat:#"%#%#%#%#%d%#",HOSTNAME,GET_NEWS,[defaults objectForKey:#"oAuthToken"],#"&limit=",FETCH_SIZE_NEWS,[NSString stringWithFormat:#"&offset=%d",self.newsList.count]];
NSURLRequest *request =[[NSURLRequest alloc] initWithURL:[NSURL URLWithString:url]];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
#if DEVELOPMENT_MODE
NSLog(#"News : %#",JSON);
NSLog(#"Response : %#\n Request : %#",response,request);
#endif
//NSLog(#"Number of news fetched : %d",((NSArray*)JSON[#"data"]).count);
for (NSDictionary *d in JSON[#"data"]){
News *new = [[News alloc] initWithDictionary:d];
[self.newsList addObject:new];
new = nil;
}
if ((((NSArray*)JSON[#"data"]).count)%FETCH_SIZE_NEWS !=0) cantFetchMore = YES;
//NSLog(#"%d cantFetch",cantFetchMore);
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
[self.tableView reloadData];
fetching = NO;
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"Request error : %# %# %#",request,error, JSON);
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
fetching = NO;
}];
[operation start];
}
This will fetch FETCH_SIZE_NEWS additional news from the server starting at the good offset which is the current size of the newsList array.
Also, if the count of fetched news % FETCH_SIZE_NEWS is different from 0, that means that we cannot fetch additional news (which will prevent from calling the webservice while scrolling the UITableView).
My issue is that when the fetching is done (exactly when I see the activity wheel running in the status bar), it blocks the GUI, and I cant continue to scroll down from the n-PADDLE_BEFORE_FETCHING cells to the n cells, or even scroll up to the previously loaded cells.
I don't really understand why as AFNetworking is supposed to run asynchronously.
Any ideas?
Thanks,
The for-loop in the completion block is running on the main thread and may be causing the slow-down. Try sending that code to another thread/queue.
As stated above buy Guy Kogus, the processing within the complete block of the AFNetworking operation was adding a bit a freezer on the main thread while scrolling.
Just added
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{//processing block}
Within the complete block (notably the foreach loop) and this is far better.
Thanks

How to fetch results from CoreData Entity

This seems like such a simple thing to do but I just can't put it together. I have an iOS app using RestKit 0.20 to populate my CoreData attributes. My main view controller is a collection view which is populated at startup by making a request to the server.
When a user selects a cell I am able to transfer the data contained in that cell to the detail view (an image and a title). But I also need to make another request to the server to obtain all of the information to be shown in the detail viewController. The Entity name is Gist and the Attribute is fullArticle
Here is the segue to the detailViewController
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"newsDetail"]) {
NSIndexPath *indexPath = [[self.collectionView indexPathsForSelectedItems] lastObject];
NSManagedObject *object = [[self fetchedResultsController] objectAtIndexPath:indexPath];
[[segue destinationViewController] setmanagedObjectContext:managedObjectContext];
[[segue destinationViewController] setDetailItem:object];
}
}
Here is what i use in -(void)viewDidLoad
{
[super viewDidLoad];
NSString *articleID =[self.detailItem valueForKey:#"articleId"];
NSString *personID = [self.detailItem valueForKey:#"userID"];
NSString *getArticle =[NSString stringWithFormat:#"/rest/article/getArticleForView?aid=%#&pid=%#&ip=255.255.255.0",articleID,personID];
[[RKObjectManager sharedManager] getObjectsAtPath:getArticle parameters:nil success:nil failure:nil];
NSURL *photoURL =[NSURL URLWithString:[self.detailItem valueForKey:#"imageUrl"]];
NSData *photoData = [NSData dataWithContentsOfURL:photoURL];
self.newsDetailImageView.image =[UIImage imageWithData:photoData];
//This is where I am stuck, How do you fetch the Attribute "fullArticle" from the request that I just made?
self.newsDetailText.text = //valueForKey:"fullArticle" from article with ID `articleID`
}
I have tried using the code below but the fetchedResultsController is setup for use with a tableView so there is no way I know of to specify without an indexPath
NSManagedObject *detailText = [self.fetchedResultsController objectAtIndex:0];
self.newsDetailText.text = [[detailText valueForKey:#"fullArticle"] description];
When tried this way fullArticle is null presumably because objectAtIndex:0 is not the right way to designate what object I need to fetch.
Please shed some light on this for me! Clearly a rookie question so code snippets really help! Thanks in advance!
Solution with help from Wain
Wain pointed out that the request does not take place immediately. I needed to use the callbacks in the request to acquire the data I was wanting. Restkit provides the mappingResults in a variety of ways. You can read about that here under RKMappingResult.
Here is how I made it work.
//I REPLACE MY SERVER REQUEST WITH THIS
[[RKObjectManager sharedManager] getObjectsAtPath:getArticle parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
//HERE IS HOW I SET THE TEXT FIELD IN THE DETAIL VIEW CONTROLLER
self.newsDetailText.text = [[mappingResult.firstObject valueForKey:#"fullArticle"]description];
// ERROR HANDLING
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
RKLogError(#"Operation failed with error: %#", error);
}];
The request you make doesn't get the results from the server immediately. You should implement the callbacks to handle success and error responses and use the supplied mapping result to get the info to display.

Custom UITableViewCell - Asynchronous UIImage for UIImageView

I have created a custom UITableViewCell which is composed by 2 UILabels and a single UIImageView.
Data associated with cells is available with a NSObject class named CellInfo. CellInfo has 2 properties of NSString type and an UIImage property.
When I create a CellInfo instance, inside the initWithData method (CellInfo class), I do the following:
if(self = [super alloc])
{
//initialize strings variables
self.name = aName;
self.descritpion = aDescription;
[self grabImage]
}
return self;
where grabImage (within CellInfo class) using ASIHTTPrequest framework to grab images in asynchronous manner (in the following code NSURL is alaways the same but in reality it changes with data)
- (void)grabImage
{
NSURL *url = [NSURL URLWithString:#"http://myurl.com/img.png"];
__block ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setCompletionBlock:^{
NSData *data = [request responseData];
UIImage* img = [[UIImage alloc] initWithData:data];
self.image = img;
[img release];
// Send a notification if image has been downloaded
[[NSNotificationCenter defaultCenter] postNotificationName:#"imageupdated" object:self];
}];
[request setFailedBlock:^{
NSError *error = [request error];
// Set default image to self.image property of CellInfo class
}];
[request startAsynchronous];
}
I have also a UITableViewController that loads data into the custom cell like the following:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Do stuff here...
// Configure the cell...
((CustomTableViewCell*)cell).nameOutlet.text = ((CellInfo*) [self.infoArray objectAtIndex:indexPath.row]).name;
((CustomTableViewCell*)cell).descriptionOutlet.text = ((CellInfo*) [self.infoArray objectAtIndex:indexPath.row]).descritpion;
((CustomTableViewCell*)cell).imageViewOutlet.image = ((CellInfo*) [self.infoArray objectAtIndex:indexPath.row]).image;
return cell;
}
In addiction, this UITableViewController observes notification from the CellInfo class because, at start up, images for visible cells are not displayed. This is the method that is called when the notification is captured:
- (void)imageUpdated:(NSNotification *)notif {
CellInfo * cInfo = [notif object];
int row = [self.infoArray indexOfObject:cInfo];
NSIndexPath * indexPath = [NSIndexPath indexPathForRow:row inSection:0];
NSLog(#"Image for row %d updated!", row);
[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationNone];
}
The code works well, but I would like to know if I'm doing right or there is a better way to do this.
My doubt is the following: is it correct to save downloaded images within each CellInfo instance or is it possible to follow another way to cache images using, for example, cache policy provided by ASIHTTPRequest?
P.S. grabImage is not called if the image for a specific CellInfo instance has already been downloaded.
I believe that's pretty neat. Instead of that you might subclass UIImageView class and create an initializer like [AsyncUIImageView initWithURL:] and then put that ASIHttpRequest logic inside the view.
After it finishes loading the picture, there could be two ways:
It can call [self setNeedsDisplay] (an UIView method) so image view is redrawn.
You can pass UITableViewCell or UITableView as a delegate to AsyncUIImgeView so that it could tell table view to reload that cell.

Resources