I'm writing an application that allows the users to take and store images on Parse. Thus far I've managed to accomplish saving the image array to Parse by using the following logic:
Take Image
Add Object to Array
Convert array to NSData
Convert NSData to PFFile
Set file upload destination (via unique objectId)
Upload PFFile to Parse
This is what the code looks like; please forgive the fact that it's in dismissViewController for now, I'm only trying to get it to save successfully:
- (void) imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
_takenImage = (UIImage *) [info objectForKey:UIImagePickerControllerOriginalImage];
[self dismissViewControllerAnimated:YES completion:^
{
// Add object to array: Working
[_tankImagesArray addObject:_takenImage];
NSLog(#"Number of images taken: %lu", (unsigned long)_tankImagesArray.count);
// Convert array to NSData Object
NSData *imageData = [NSKeyedArchiver archivedDataWithRootObject:_tankImagesArray];
// Convert NSData Object to PFFile
PFFile *imageFile = [PFFile fileWithData:imageData];
PFQuery *tankQuery = [PFQuery queryWithClassName:#"SavedTanks"];
_tankObject = [tankQuery getObjectWithId:_passedValue];
[_tankObject setObject:imageFile forKey:#"tankImages"];
[_tankObject save];
}];
}
Now, my question is: How exactly would I go about retrieving that file? My ultimate goal here is to allow the user to see images they've taken in the past and add to the list of pictures in the collection and upload them to the server. I'm just unsure of how to retrieve the file once its been uploaded and make sure the integrity is maintained.
Did you try:
PFQuery *query = [PFQuery queryWithClassName:#"SavedTanks"];
[query whereKey:#"tankImages" equalTo:#"your_image.jpg"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
// The find succeeded.
NSLog(#"Successfully retrieved %d images.", objects.count);
// Do something with the found objects
for (PFObject *object in objects) {
NSLog(#"%#", object.objectId);
}
} else {
// Log details of the failure
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
PFQuery *query = [PFQuery queryWithClassName:#"SavedTanks"];
// Add constraints here to get the image you want (like the objectId or something else)
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
for (PFObject *object in objects) {
PFFile *imageFile = object[#"tankImages"];
[imageFile getDataInBackgroundWithBlock:^(NSData *imageData, NSError *error) {
if (!error) {
UIImage *image = [UIImage imageWithData:imageData]; // Here is your image. Put it in a UIImageView or whatever
}
}];
}
} else {
// Log details of the failure
}
}];
In the .h file of your collection view you need to have something like below. Note that the one I built you could like an image and then sort liked images using a segment controller.
#import <UIKit/UIKit.h>
#import "UICollectionCell.h"
#import <Parse/Parse.h>
#interface ParseViewController : UIViewController {
NSArray *imageFilesArray;
NSMutableArray *imagesArray;
}
#property (weak, nonatomic) IBOutlet UICollectionView *imagesCollection;
- (IBAction)segmentSelected:(id)sender;
#property (weak, nonatomic) IBOutlet UISegmentedControl *segmentedController;
#end
Then in the .m file of your collection view
#interface ParseViewController ()
#end
#implementation ParseViewController
#synthesize imagesCollection;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
[self queryParseMethod];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
// code to add the number of images etc as per table view
-(void) queryParseMethod {
NSLog(#"start query");
PFQuery *query = [PFQuery queryWithClassName:#"collectionView"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
imageFilesArray = [[NSArray alloc] initWithArray:objects];
NSLog(#"%#", imageFilesArray);
[imagesCollection reloadData];
}
}];
}
#pragma mark - UICollectionView data source
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
// number of sections
return 1;
}
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
// number of items
return [imageFilesArray count];
}
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
// the custom cell we named for the reusable identifier
static NSString *cellIdentifier = #"imageCell";
UICollectionCell *cell = (UICollectionCell *)[collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
PFObject *imageObject = [imageFilesArray objectAtIndex:indexPath.row];
PFFile *imageFile = [imageObject objectForKey:#"imageFile"];
// show loading spinner
[cell.loadingSpinner startAnimating];
cell.loadingSpinner.hidden = NO;
[imageFile getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
if (!error) {
NSLog(#"%#", data);
cell.parseImage.image = [UIImage imageWithData:data];
[cell.loadingSpinner stopAnimating];
cell.loadingSpinner.hidden = YES;
}
}];
return cell;
}
-(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
[self likeImage:[imageFilesArray objectAtIndex:indexPath.row]];
}
-(void) likeImage:(PFObject *)object {
[object addUniqueObject:[PFUser currentUser].objectId forKey:#"favorites"];
[object saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (!error) {
NSLog(#"liked picture!");
[self likedSuccess];
}
else {
[self likedFail];
}
}];
}
-(void) likedSuccess {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Success" message:#"You have succesfully liked the image" delegate:self cancelButtonTitle:#"OK" otherButtonTitles: nil];
[alert show];
}
-(void) likedFail {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Unsuccesfull" message:#"You have been unable to like the image" delegate:self cancelButtonTitle:#"OK" otherButtonTitles: nil];
[alert show];
}
/*
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
- (IBAction)segmentSelected:(id)sender {
if (_segmentedController.selectedSegmentIndex == 0) {
[self queryParseMethod];
}
if (_segmentedController.selectedSegmentIndex == 1) {
[self retrieveLikedImages];
}
}
-(void) retrieveLikedImages {
PFQuery *getFavorites = [PFQuery queryWithClassName:#"collectionView"];
[getFavorites whereKey:#"favorites" equalTo:[PFUser currentUser].objectId];
[getFavorites findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
imageFilesArray = [[NSArray alloc] initWithArray:objects];
[imagesCollection reloadData];
}
}];
}
#end
Hope this is of some help to you.
All the above solutions are correct, but want to add one another way to have support of Image Caching of SDWebImage or any library like that.
On Successful completion you will be have PFFile, whose property "url" will return you actuall URL of Image where it is saved. You can use that to load image. Using this approach I was able to have Image Cacheing based on key as URL.
...
NSString *strUrl = pfFileObject.url;
...
...
[img sd_setImageWithURL:[NSURL URLWithString:strUrl]];
Why would you want to download photos from parse that the user already has them locally..?
I recommend you to use : https://github.com/AFNetworking/AFNetworking
you can also save local photos to cache so you access them easily so you dont need any downloading from parse...
now if you still want to download the photos from parse just make a normal query and download all the photos parse object and you will get in the PFObject the PFFile of your photos.
Example:
PFQuery *query = [PFQuery queryWithClassName:#"SavedTanks"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
for(PFObject *obj in objects){
PFFile *file = [obj objectForKey:#"tankImages"];
// now you can use this url to download the photo with AFNetwork
NSLog(#"%#",file.url);
}
}
}];
Related
I'm new to Firebase, and I have been following their documentation here.
However, nothing I have tried seems to work.
What I am trying to do:
1) Register a user -Works
2) Have the user choose a profile picture during the registration process -Doesn't work.
The code:
- (void) registerNewUser:(FIRUser *)user
{
FIRUser *currentUser = [FIRAuth auth].currentUser;
NSString *email = emailAddressTxtField.text;
NSString *password = passwordTxtField.text;
NSString *username = usernameTxtField.text;
[[FIRAuth auth]
createUserWithEmail:email
password:password
completion:^(FIRUser *_Nullable user,
NSError *_Nullable error)
{
if (error)
{
NSLog(#"%#", error.localizedDescription);
return;
}
else
{
////ASSIGN NEW USER THEIR NAME////
self.databaseRef = [[FIRDatabase database] reference];
[[[databaseRef child:#"users"] child:user.uid]
setValue:#{#"name": username}];
}
}];
////ESTABLISHED A USER, SO LET'S ASSIGN THEIR PIC TO THEM////
if (profilePicImageView.image)
{
FIRStorageReference *profilePicRef = [[storageRef child:#"images/profilePicture.jpg"] child:currentUser.uid];
FIRStorageMetadata *metadata = [[FIRStorageMetadata alloc] init];
metadata.contentType = #"image/jpeg";
NSData *imageData = UIImageJPEGRepresentation(self.profilePicImageView.image, 0.8);
NSLog(#"metadata from image: %#", metadata);
[profilePicRef putData:imageData metadata:metadata completion:^(FIRStorageMetadata *metadata, NSError *error)
{
if (error != nil)
{
NSString *profileImageURL = metadata.downloadURL.absoluteString;
NSLog(#"Profile Image URL from image: %#", profileImageURL);
[ProgressHUD showSuccess:[NSString stringWithFormat:#"%# Successfully Registered!!", username]];
[self.segmentedLoginRegister setSelectedSegmentIndex:0];
[self checkSegmentedControl];
[ProgressHUD showSuccess:[NSString stringWithFormat:#"Welcome %#!", username]];
}
else
{
NSLog(#"Failed to Register User with profile image");
}
}];
}
}
Additional Information:
Photos are coming only from the camera roll of the user's device
Debug area prints:
[Generic] Creating an image format with an unknown type is an error
My guess is that profilePicImageView.image doesn't actually contain a valid image object.
HOW TO REGISTER A USER
Let's assume that you have 3 textfields to register a user. You have a usernameTextfield, an emailTextField, and a passwordTextfield. We also want to have a profilePicture associated with this user. So we first:
Established a method to save our values for our User to the Firebase Database:
-(void) saveValuesForUser:(FIRUser *) user
{
NSString *username = usernameTxtField.text;
self.databaseRef = [[FIRDatabase database] reference];
[[[databaseRef child:#"users"] child:user.uid]
setValue:#{#"Name": username, #"Profile Picture": profileImageURL}];
}
The profileImageURL above is an NSString that you can create above your implementation of your view controller, but we also should create a UIImage that we can access throughout the ViewController:
#import "ViewController.h"
#import Photos;
#interface ViewController ()
#end
NSString *profileImageURL;
UIImage *profileImage;
#implementation ViewController
I'm guessing that you know how to launch the camera roll off from a UIButton but in case you do not, let's call these methods so that when our user taps the button to choose their image that it get's set to our profilePicture.imageView:
- (IBAction)chooseImageAction:(id)sender
{
UIImagePickerController *picker = [[UIImagePickerController alloc] init];
picker.delegate = self;
picker.allowsEditing = YES;
picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
[self presentViewController:picker animated:YES completion:NULL];
}
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info
{
picker.delegate = self;
profileImage = info[UIImagePickerControllerEditedImage];
profilePicImageView.image = profileImage;
[self dismissViewControllerAnimated:NO completion:nil];
}
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
{
[picker dismissViewControllerAnimated:YES completion:NULL];
}
Now we're cooking with fire! Now that we have the image the user wants to upload for their profilePicture, let's create a method to save it as an NSURL to our Firebase Database:
- (void) saveProfileImage
{
FIRUser *currentUser = [FIRAuth auth].currentUser;
NSString *username = usernameTxtField.text;
if (profilePicImageView.image != nil)
{
FIRStorage *storage = [FIRStorage storage];
storageRef = [storage referenceForURL:#"gs://PUTYOURFIREBASEHERE.appspot.com"];
NSString *imageID = [[NSUUID UUID] UUIDString];
NSString *imageName = [NSString stringWithFormat:#"Profile Pictures/%#.jpg",imageID];
FIRStorageReference *profilePicRef = [storageRef child:imageName];
FIRStorageMetadata *metadata = [[FIRStorageMetadata alloc] init];
metadata.contentType = #"image/jpeg";
NSData *imageData = UIImageJPEGRepresentation(self.profilePicImageView.image, 0.8);
[profilePicRef putData:imageData metadata:metadata completion:^(FIRStorageMetadata *metadata, NSError *error)
{
if (!error)
{
profileImageURL = metadata.downloadURL.absoluteString;
[self saveValuesForUser: currentUser];
}
else if (error)
{
NSLog(#"Failed to Register User with profile image");
}
}];
}
}
IMPORTANT: Make sure you insert your gs:// reference for the value of storageRef! You can find this on your Firebase Console under Storage.
Now that we have the method to save the user's profile picture, let's create a method to Register the new user that we can call it on our IBAction:
- (void) registerNewUser
{
NSString *email = emailAddressTxtField.text;
NSString *password = passwordTxtField.text;
[[FIRAuth auth]
createUserWithEmail:email
password:password
completion:^(FIRUser *_Nullable user,
NSError *_Nullable error)
{
if (error)
{
NSLog(#"%#", error.localizedDescription);
return;
}
else
{
[self saveProfileImage];
}
}];
}
Oh thats nice! Now, let's call that method on our button, so that when the user taps the Register button on our UI, that it get's called:
- (IBAction)registerUserAction:(id)sender
{
[self registerNewUser];
}
Note: Make sure that in your ViewController.h file that you are setting the appropriate delegates:
#interface ViewController : UIViewController <UIImagePickerControllerDelegate, UINavigationControllerDelegate, UITextFieldDelegate>
So I am using Parse to link a user with their twitter account. In the app delegate I have the following:
[PFTwitterUtils initializeWithConsumerKey:CONSUMER_KEY consumerSecret:CONSUMER_SECRET];
Then the button which the user clicks to link the user to facebook calls the following:
-(IBAction)twitterConnectPressed{
NSLog(#"twitter");
[PFTwitterUtils linkUser:[PFUser currentUser] block:^(BOOL succeeded, NSError* error){
NSLog(#"haha");
if(succeeded){
UIAlertView* alert = [[UIAlertView alloc] initWithTitle:#"Done!" message:#"Connected with Twitter!" delegate:self cancelButtonTitle:#"okay" otherButtonTitles: nil];
[alert show];
self.fbButton.backgroundColor = [TGAPublic grey];
}else{
UIAlertView* alert = [[UIAlertView alloc] initWithTitle:#"Oops" message:error.userInfo[#"error"] delegate:self cancelButtonTitle:#"okay" otherButtonTitles: nil];
[alert show];
}
}];
}
However even though linkUser:block: is called it doesn't do anything at all. It doesn't create a pop up to log in to twitter like [PFFacebookUtils linkUser:] does and therefore doesn't end up calling the block either
PFTwitterUtils does not appear to handle all cases on iOS. In particular, if you do not have an account setup (Settings->Twitter) it does not fire up a web view and attempt to used web oauth. Conversely if you have multiple Twitter accounts configured (again in Settings) then it doesn't appear to fire up an action sheet to allow you to select which account you'd like to link.
There's a great tutorial on how to do these things which exposes an extension to PFFacebookUtils here: http://natashatherobot.com/ios-twitter-login-parse/
It does not do linking though, just login, but should be a good basis to add linking.
I've got similar problem with link/unlink methods for both PFFacebookUtils and PFTwitterUtils (v. 1.7.4).
The only way I managed to make it work was to replace them by, unfortunately, messing with internal Parse implementation of authData:
#import "TwitterAuthProvider.h"
#import "PFTwitterUtils.h"
#import "PFUser.h"
static NSString * const kTwitterKey = #"XXX";
static NSString * const kTwitterSecret = #"XXX";
#implementation TwitterAuthProvider
- (instancetype)init {
if ((self = [super init])) {
[PFTwitterUtils initializeWithConsumerKey:kTwitterKey consumerSecret:kTwitterSecret];
}
return self;
}
- (void)setAuthData:(id)twAuthData forUser:(PFUser *)user {
static NSString * const kParseAuthDataKey = #"authData";
static NSString * const kParseLinkedServiceNamesKey = #"linkedServiceNames";
static NSString * const kParseAuthProviderName = #"twitter";
NSMutableDictionary *authData = [[user valueForKey:kParseAuthDataKey] mutableCopy] ?: [NSMutableDictionary dictionary];
authData[kParseAuthProviderName] = twAuthData ?: [NSNull null];
[user setObject:authData forKey:kParseAuthDataKey];
[user setValue:authData forKey:kParseAuthDataKey];
NSMutableSet *linkedServices = [[user valueForKey:kParseLinkedServiceNamesKey] mutableCopy] ?: [NSMutableSet set];
if (twAuthData) {
[linkedServices addObject:kParseAuthProviderName];
} else {
[linkedServices removeObject:kParseAuthProviderName];
}
[user setValue:linkedServices forKey:kParseLinkedServiceNamesKey];
}
- (void)linkWithCompletion:(PFBooleanResultBlock)completion {
NSParameterAssert(completion != nil);
PFUser *user = [PFUser currentUser];
__weak typeof(self) weakSelf = self;
PF_Twitter *twitter = [PFTwitterUtils twitter];
[twitter authorizeWithSuccess:^(void) {
[weakSelf setAuthData:[self twitterAuthData] forUser:user];
[user saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (!succeeded) {
//revert
[weakSelf setAuthData:nil forUser:user];
}
completion(succeeded, error);
}];
} failure:^(NSError *error) {
completion(NO, error);
} cancel:^(void) {
completion(NO, nil);
}];
}
- (void)unlinkWithCompletion:(PFBooleanResultBlock)completion {
NSParameterAssert(completion != nil);
PFUser *user = [PFUser currentUser];
[self setAuthData:nil forUser:user];
[user saveInBackgroundWithBlock:completion];
}
- (NSDictionary *)twitterAuthData {
PF_Twitter *twitter = [PFTwitterUtils twitter];
return #{
#"auth_token" : twitter.authToken,
#"auth_token_secret": twitter.authTokenSecret,
#"consumer_key": kTwitterKey,
#"consumer_secret": kTwitterSecret,
#"id": twitter.userId,
#"screen_name": twitter.screenName,
};
}
#end
I have a button for each cell, and once clicked it deletes each individual object from the Parse backend. I have it set to reloadData upon success in the block as seen here:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
PFRelation *relation = [self.currentUser relationforKey:#"watching"];
[[relation query] findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (error) {
// There was an error
} else {
// NSLog(#"%#", objects);
self.watchingEvents = objects;
[self refreshTableView];
}
}];
}
-(IBAction) deleteButtonAction:(id) sender
{
[SVProgressHUD showWithStatus:#"Removing from Watch List..."];
PFRelation *relation = [self.currentUser relationforKey:#"watching"];
[relation removeObject:self.selectedEvent];
[self.currentUser saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (error)
{
NSString *errorString = [[error userInfo] objectForKey:#"error"];
UIAlertView *errorAlertView = [[UIAlertView alloc] initWithTitle:#"Error" message:errorString delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[errorAlertView show];
}
else
{
[SVProgressHUD showSuccessWithStatus:#"Removed from Watch List!"];
[self refreshTableView];
}
}];
}
-(void)refreshTableView
{
[self.tableView reloadData];
}
But it doesn't seem to update the tableView, the cell still shows up (even though it's gone because if I navigate away and open the view again the viewWillAppear gets called with the same reloadData function and the items gone like how it should be. So I know the functions working, since it's clearly working in viewWillAppear. Not sure why it's not working once the success of the block is run, as the alert runs fine as does any NSLog message I put in?
Change you refresh method to:
-(void)refreshTableView
{
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
}
you are deleting the data from the backend but you are not deleting that object from self.watchingEvents. and i think u must be using self.watchingEvents to populate the date in cellForRowAtindex of your table. Please delete that object from self.watchingEvents too on deletion and your problem should be solved.
You need to use [tableView beginUpdates] before you do delete, insert or move actions and then [tableView endUpdates] at the end.
- (void)beginUpdates;
- (void)endUpdates;
https://developer.apple.com/Library/ios/documentation/UIKit/Reference/UITableView_Class/Reference/Reference.html#//apple_ref/occ/instm/UITableView/beginUpdates
I continue refining the implementation of my UICollectionViewController with Parse and this time I'm dealing with an issue that it might be related to cache or maybe the reloadData method itself.
Maybe you can help me identify the source of this strange behavior that I better show you on a short video to save time:
Refreshing issue video
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
refreshControl = [[UIRefreshControl alloc] init];
[refreshControl addTarget:self action:#selector(refershControlAction) forControlEvents:UIControlEventValueChanged];
[self.collectionView addSubview:refreshControl];
[self queryForTable];
}
Then on my refreshControlAction:
- (void)refershControlAction
{
NSLog(#"Reload grid");
// The user just pulled down the collection view. Start loading data.
[self queryForTable];
[refreshControl endRefreshing];
}
The query method is like this:
- (void)queryForTable
{
PFQuery *query = [PFQuery queryWithClassName:#"Photo"];
query.limit = 15;
[query orderByAscending:#"createdAt"];
[query setCachePolicy:kPFCachePolicyNetworkOnly];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
// The find succeeded.
NSLog(#"Successfully retrieved %d photos.", objects.count);
[self.collectionView reloadData];
gridImages = [[NSMutableArray alloc] initWithCapacity:objects.count];
// Do something with the found objects
for (PFObject *object in objects) {
PFFile *thumbnail = [object objectForKey:#"thumbnail"];
[thumbnail getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
if (!error) {
// Now that the data is fetched, update the cell's image property with thumbnail
//NSLog(#"Fetching image..");
[gridImages addObject:[UIImage imageWithData:data]];
//NSLog(#"Size of the gridImages array: %d", [gridImages count]);
} else {
// Log details of the failure
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
}
} else {
// Log details of the failure
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
}
This doesn't happen on my PFQueryTableViewController where I'm performing the exact same query and where I'm also using the iOS 6 refresh control instead of the one provided by Parse.
Do you see something that could be causing this behavior?
I could see some prob in your code.
- (void)refershControlAction
{
NSLog(#"Reload grid");
// The user just pulled down the collection view. Start loading data.
[self queryForTable];
[refreshControl endRefreshing];
}
you endRefreshing before your query get completed, so it is wrong use. You should put [refreshControl endRefreshing] in your-(voi)queryForTable` when the query complete
The other problem is I don't know if you get your datasource updated when the query completed.
I have a UITableViewController that is in charge of displaying a table full of employees. This data is being stored on a database on parse.com.
This is my UITableViewController, in which I just initiate the store:
-(id) initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil{
self = [super initWithNibName:nil bundle:nil];
if(self){
store = [[EmployeeStore alloc] init];
}
return self;
}
This is the EmployeeStore init method, in which i query for the employees:
-(id) init{
self = [super init];
if(self){
employees = [[NSMutableArray alloc] init];
[self fetchEmployeesFromDatabase];
}
return self;
}
fetchEmployeesFromDatabase, where I query for the employees.
-(void) fetchEmployeesFromDatabase{
PFQuery *query = [PFQuery queryWithClassName:#"Employee"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
// The find succeeded.
NSLog(#"Successfully retrieved %d scores.", objects.count);
// Do something with the found objects
for (PFObject *object in objects) {
NSLog(#"%#", object.objectId);
[employees addObject:object];
}
} else {
// Log details of the failure
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
}
I am successfully receiving them, however the problem is the query takes place in the background, and does not finish until after the table view is done loading, so the table view does not populate. I need to have the table reload its data after the query is complete but how do I go about doing this?
UITableViewController
-(id) initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil{
self = [super initWithNibName:nil bundle:nil];
if(self){
store = [[EmployeeStore alloc] init];
store.tableView = self.tableView;
}
return self;
}
EmployeeStore.h
#property (nonatomic, strong) UITableView *tableView;
EmployeeStore.m
-(id) init{
self = [super init];
if(self){
employees = [[NSMutableArray alloc] init];
[self fetchEmployeesFromDatabase];
}
return self;
}
and fetchEmployeesFromDatabase
-(void) fetchEmployeesFromDatabase{
PFQuery *query = [PFQuery queryWithClassName:#"Employee"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
// The find succeeded.
NSLog(#"Successfully retrieved %d scores.", objects.count);
// Do something with the found objects
for (PFObject *object in objects) {
NSLog(#"%#", object.objectId);
[employees addObject:object];
}
dispatch_async(dispatch_get_main_queue(), ^ {
[self.tableView reloadData];
});
} else {
// Log details of the failure
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
}
When the load completes tell the table view to reload.
if (!error) {
// The find succeeded.
NSLog(#"Successfully retrieved %d scores.", objects.count);
// Do something with the found objects
for (PFObject *object in objects) {
NSLog(#"%#", object.objectId);
[employees addObject:object];
}
dispatch_async(dispatch_get_main_queue(), ^ {
[tableView reloadData];
});
} else {
You need to ensure that your employees store is thread safe or is not accessed by the background and main threads concurrently.
-(void) fetchEmployeesFromDatabase{
PFQuery *query = [PFQuery queryWithClassName:#"Employee"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
// The find succeeded.
NSLog(#"Successfully retrieved %d scores.", objects.count);
// Do something with the found objects
for (PFObject *object in objects) {
NSLog(#"%#", object.objectId);
[employees addObject:object];
}
} else {
// Log details of the failure
NSLog(#"Error: %# %#", error, [error userInfo]);
}
[self performSelectorOnMainThread:#selector(reloadTableView) withObject:nil waitUntilDone:NO];
}];
}
-(void)reloadTableView{
[self.aTableview reloadData];
}
Now you load your tableview on mainthread. Try this.
Here the data gets populated into the table even before the table view is completed.
All you need to do here is place the statement [self fetchEmployeesFromDatabase]; inside viewDidLoad() method or whichever method which invokes your table creation .
I had this problem, except for collection view. After a lot of pain, this did the trick to reload the data via the main thread.
dispatch_async(dispatch_get_main_queue(), ^ {
[self.collectionView reloadData];
[self.collectionView reloadItemsAtIndexPaths:[self.collectionView indexPathsForVisibleItems]];
});