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
Related
This is the code I have as part of my ViewDidLoad
I'm trying to do something in the part of the code where i have the NSLogs which aren't be executed. I haven't been able to find anyone with the same issue?
Where am I going wrong? Thanks in advance!
PFRelation *relation = [staffMember relationForKey:#"completedTraining"];
PFQuery *query = [relation query];
[query includeKey:#"trainingRecordPointer"];
[query findObjectsInBackgroundWithBlock:^(NSArray *completedTrainingRecords, NSError *error){
if(!error){
for (PFObject* completedTrainingRecord in completedTrainingRecords) {
PFObject * recordWtihTypeAndName = [completedTrainingRecord objectForKey:#"trainingRecordPointer"];
PFObject *outputObject = [[PFObject alloc]initWithClassName:#"NewTrainingRecord"];
NSString *recordName = [recordWtihTypeAndName valueForKey:#"RecordName"];
[completeRecordsWithType addObject:recordName];
[outputObject addObject:recordName forKey:#"recordName"];
[outputObject addObject:completedTrainingRecord.createdAt forKey:#"date"];
[[trainingRecordsDictionary objectForKey:[recordWtihTypeAndName objectForKey:#"Type"]] addObject:outputObject];
[self.tableView reloadData]; //it works up to this point but if I move this line outside
//the for-loop nothing happens
NSLog(#"this will execute"); // does execute
}
NSLog(#"this wont execute"); // doesn't execute
} else {
NSLog(#"Error: %# %#", error, [error userInfo]);
}
NSLog(#"this wont execute"); // doesn't execute
}];
You should move [self.tableView reloadData]; to outside your for-loop.
You should also make sure the tableview is reloaded on the mainthread, and not in this background thread.
Maybe like this:
[query findObjectsInBackgroundWithBlock:^(NSArray *completedTrainingRecords, NSError *error){
if(!error){
for (PFObject* completedTrainingRecord in completedTrainingRecords) {
... do your stuff ...
}
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^ {
[weakSelf.tableView reloadData];
});
}
}];
You probably get into trouble because you try to modify your UI on a backgroundthread.
I have an UITableView, with cells that you can tap on.
When you tap, some actions are run, but that is not important in this context. First time I press, it handles the tap correctly. Second time it is running the code, but all View updates like showing a UIAlertView or showing a new view is delayed. But not delayed by time - It's waiting for me to touch the screen. No matter where I press or how long I wait, I just have to press. And it's every time but the first.
My TableView is set to single selection and not to show selection on touch. Any Ideas why it does this?
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
if ([cell isKindOfClass:[DetailTableViewCell class]]) {
DetailTableViewCell *detailCell = (DetailTableViewCell *)cell;
NSString *hourString = [detailCell hourString];
if (!detailCell.booking) {
NSDate *rightNow = [NSDate new];
NSDate *cellDate = [self.currentDate dateWithHour:indexPath.row andMinutes:0];
// Only allow future bookings (but allow people people to book within the hour)
if([rightNow compare:[cellDate nextHour]] == NSOrderedAscending){
[self performSegueWithIdentifier:#"roomBooking" sender:indexPath];
return;
} else {
[[[UIAlertView alloc] initWithTitle:#"Error" message:#"We currently do not allow our users make bookings in the past" delegate:nil cancelButtonTitle:#"Gotcha" otherButtonTitles:nil] show];
return;
}
} else if ([detailCell.booking hasPhoneNumber]) {
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:[NSString stringWithFormat:#"telprompt://%ld",(long)[detailCell.booking telephone]]]];
return;
} else {
//TODO: FIND OUT IF BOOKING IS OWNED BY THE CURRENT USER
[[[UIAlertView alloc] initWithTitle:#"Booking"
message:[NSString stringWithFormat:#"You are now removing the booking at %#.", hourString]
delegate:nil
cancelButtonTitle:#"OK" otherButtonTitles:nil] show];
#weakify(self);
[self.room removeBookingWithId:[detailCell.booking.bookingId integerValue] andCompletion:^(BOOL success, NSError *error) {
#strongify(self);
if (success) {
#weakify(self);
[self.room.location updateBookingsWithCompletion:^(BOOL success, NSError *error) {
#strongify(self);
if (success) {
[self.calendar reloadData];
}
}];
}
}];
return;
}
}
}
SelectionStyle was set to None, but we changed it to something else, which solved the problem.
Click on the TableViewCell itself.
In your properties section locate: "Selection"
Set to: Blue/Grey/Default.
I am using the following code to update a Parse object as button action:
-(IBAction)sendPressed:(id)sender
{
NSLog(#"boton subir cadena pulsado");
loadingSpinner.hidden = NO;
[loadingSpinner startAnimating];
//Upload a new picture
NSData *pictureData = UIImagePNGRepresentation(self.chainPhoto.image);
PFFile *file = [PFFile fileWithName:#"img" data:pictureData];
[file saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (succeeded){
NSLog(#"IMAGEN CARGADA");
PFQuery *query = [PFQuery queryWithClassName:#"cadenas"];
// Retrieve the object by id
[query getObjectInBackgroundWithId: chain.objectId block:^(PFObject *imageObject, NSError *error)
{
[imageObject setObject:file forKey:#"image"];
[imageObject setObject:self.commentTextField.text forKey:#"chain_name"];
[imageObject saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (succeeded){
//Go back to the wall
[self.navigationController popViewControllerAnimated:YES];
}
else{
NSString *errorString = [[error userInfo] objectForKey:#"error"];
[self showErrorView:errorString];
}
}];
}
ERROR HERE--> else
{
NSString *errorString = [[error userInfo] objectForKey:#"error"];
[self showErrorView:errorString];
}
}
[loadingSpinner stopAnimating];
//loadingSpinner.hidden = YES;
//self.commentTextField.text =#" ";
self.progress_block.hidden = YES;
// self.imageView.image = [UIImage imageNamed:#"no-image.jpg"];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Restaurant Chain changed with success"
message:#"You can now go back to the list."
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
} progressBlock:^(int percentDone) {
self.progress_block.hidden =NO;
self.progress_block.progress = (float) percentDone/100+progressValue;
}];
}
In the line that I have marked as ERROR HERE in the code, there is an error warning (Expected ":"), but I can't find out why.
Any help is welcome.
From the looks of it, you never closed the query bracket:
PFQuery getObjectInBackground.... {
but never closed it using the proper syntax or it looks like you have an extra bracket }. For better practice, you should use proper indentation with if statements or else complications can happen like this. You get lost in the code because you don't know where a statement begins or ends
You should close it after the else statement so:
} ERROR HERE--> else {
NSString *errorString = [[error userInfo] objectForKey:#"error"];
[self showErrorView:errorString];
//stop animating and other stuff
}
}];
I can't troubleshoot because i'm on my iPhone but I would suggest going back and using proper indentation so you can catch your culprit
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);
}
}
}];
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.