Say I have a PFObject which I am editing. At later stage I wish to cancel the changes I have done to the PFObject. How do I revert back to original copy of PFObject?
What I have tried
if (self.request.isDirty) { // self.request is a PFObject
// Reload object
NSLog(#"%#", self.request.requestTitle); // Logs ABC, Original was DEF
[self.request refreshInBackgroundWithBlock:^(PFObject *object, NSError *error) {
if (!error) {
NSLog(#"%#", self.request.requestTitle); // Logs ABC
[self.requestDetailsTableView reloadData];
}
}];
}
I tried fetching the object as well, but same result
if (self.request.isDirty) { // self.request is a PFObject
// Reload object
NSLog(#"%#", self.request.requestTitle); // Logs ABC, Original was DEF
[self.request fetchInBackgroundWithBlock:^(PFObject *object, NSError *error) {
if (!error) {
NSLog(#"%#", self.request.requestTitle); // Logs ABC
[self.requestDetailsTableView reloadData];
}
}];
}
There is now a revert method on PFObject which will reset your object back to the server state as long as you haven't called save: http://parseplatform.org/Parse-SDK-iOS-OSX/api/Classes/PFObject.html#/c:objc(cs)PFObject(im)revert
For example, I use a pushed view controller with a form to edit properties on my PFObject subclass and I have two buttons for Cancel and Save...
#IBAction override func cancelAction() {
self.myObject?.revert()
self.navigationController?.popViewControllerAnimated(true)
}
#IBAction override func saveAction() {
self.myObject?.saveInBackgroundWithBlock({ (succeeded: Bool, error: NSError?) -> Void in
if (succeeded) {
self.navigationController?.popViewControllerAnimated(true)
} else {
// Show error
}
})
}
There isn't currently a way to do this directly - you need to do a little fancy footwork with your calls :)
Try this:
if (self.request.isDirty) { // self.request is a PFObject
// Reload object
NSLog(#"%#", self.request.requestTitle); // Logs ABC, Original was DEF
PFQuery *newRequest = [PFQuery queryWithClassName:#"YourClassName"];
//Add your request parameters here...
[newRequest getFirstObjectInBackgroundWithBlock:^(PFObject *object, NSError *error) {
if (!error)
self.request = object;
NSLog(#"%#", self.request.requestTitle); // Should log DEF
[self.requestDetailsTableView reloadData];
}
}];
}
Related
I currently have a ParseManager class setup to handle the signin for a particular user (wanted to avoid tight coupling with a view controller).
I'm trying to figure out a way to notify the view controller that the login has completed successfully that ensures a good design. Here is what I have so far:
ParseManager:
- (BOOL) signUpParse:(NSDictionary *)loginDetails
{
PFUser *newUser = [PFUser user];
newUser.username = loginDetails[#"username"];
newUser.password = loginDetails[#"password"];
[newUser signUpInBackgroundWithBlock:^(BOOL succeeded, NSError * _Nullable error) {
if(!error) {
//i want to let the view controller know that the signup has completed here
} else {
NSString *errorString = [error userInfo][#"error"];
}
}];
}
There are many ways you could do this.
Delegates
One way would be to use delegates. Set up a ParseManagerDelegate, for example
#protocol ParseManagerDelegate <NSObject>
- (void)signInWasSuccessful;
#end
And then create a delegate property on ParseManager
#property (weak, nonatomic) id<ParseManagerDelegate> delegate;
Set this property in your view controller
parseManager.delegate = self;
You can then call the delegate method like this
[newUser signUpInBackgroundWithBlock:^(BOOL succeeded, NSError * _Nullable error) {
if(!error) {
[self.delegate signInWasSuccessful];
} else {
NSString *errorString = [error userInfo][#"error"];
}
}];
In your view controller, you would implement the delegate method like this
- (void)signInWasSuccessful
{
//handle log in
}
You also have to make your view controller conform to the ParseManagerDelegate protocol
#interface ViewController : UIViewController <ParseManagerDelegate>
Notifications
Another way would be to use notifications. I wouldn't necessarily recommend this for a log in notification, but you could do it in the following manner.
In your view controller, put the following line somewhere
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(handleLogInMethod) name:#"LogInNotification" object:nil];
And then call that notification like this
[newUser signUpInBackgroundWithBlock:^(BOOL succeeded, NSError * _Nullable error) {
if(!error) {
[[NSNotificationCenter defaultCenter] postNotificationName:#"LogInNotification"];
} else {
NSString *errorString = [error userInfo][#"error"];
}
}];
When the user has successfully logged in, - handleLogInMethod will be called on the view controller.
Blocks
A third way to do this would be to use blocks. You could define a new type for your block like this
typedef void (^ParseManagerLogInBlock)();
Create a property on ParseManager for the block
#property (strong, nonatomic) ParseManagerLogInBlock logInBlock;
And then call the block when log in was successful
[newUser signUpInBackgroundWithBlock:^(BOOL succeeded, NSError * _Nullable error) {
if(!error) {
self.logInBlock();
} else {
NSString *errorString = [error userInfo][#"error"];
}
}];
You would set this block in your view controller
parseManager.logInBlock = ^{
//handle log in here
};
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've been struggling on this for several days so any would be appreciated. I'm trying to save the players array below and display it in a UITableView. I'd like to save it so I can display the local player's friends. I've tried several different things but something that looks it's working for others is this.
__block NSArray *friends;
- (void) loadPlayerData: (NSArray *) identifiers {
[GKPlayer loadPlayersForIdentifiers:identifiers withCompletionHandler:^(NSArray *players, NSError *error) {
if (error != nil) {
// Handle the error.
}
if (players != nil) {
friends = players;
NSLog(#"Inside: %#", friends); //Properly shows the array
}
}];
NSLog(#"Outside: %#", friends): //Doesn't work, shows nil
}
But friends is still nil/null afterwards. Am I doing something wrong? Is there any way to save players and use it in a UITableView? Thanks.
***EDIT***
So here's the solution I put together.
typedef void(^CallbackBlock)(id object);
+ (void) retrieveFriends: (CallbackBlock)callback {
GKLocalPlayer *lp = [GKLocalPlayer localPlayer];
if (lp.authenticated) {
[lp loadFriendsWithCompletionHandler:^(NSArray *friends, NSError *error) {
if (friends != nil) {
[self loadPlayerDataWithIdentifiers:friends callback:^(NSArray *playersInfo) {
if (callback) callback(playersInfo);
}];
}
}];
}
}
+ (void) loadPlayerDataWithIdentifiers: (NSArray *) identifiers callback:(CallbackBlock)callback {
[GKPlayer loadPlayersForIdentifiers:identifiers withCompletionHandler:^(NSArray *players, NSError *error) {
if (error != nil) {
// Handle the error.
}
if (players != nil) {
if (callback) callback(players);
}
}];
}
The only thing is, my UITableView is in another class so I tried doing this and making the two methods above public. info isn't printing out anything. Any ideas?
[GameCenterHelper retrieveFriends:^(NSArray *info) {
NSLog(#"Friends Info: %#", info);
}];
Use callback blocks.
typedef void(^CallbackBlock)(id object);
- (void)loadPlayerDataWithIdentifiers:(NSArray *)identifiers callback:(CallbackBlock)callback {
[GKPlayer loadPlayersForIdentifiers:identifiers withCompletionHandler:^(NSArray *players, NSError *error) {
if (error != nil) {
// Handle the error.
}
if (callback) callback(players);
}];
}
You imply in your question that this is for a table view. If so, you need to reload your table after the data has been loaded.
[self loadPlayerDataWithIdentifiers:identifiers callback:^(NSArray *players) {
self.players = players;
[self.tableView reloadData];
}];
Crimson Chris is correct.
The other option is use GCD to wait for the response to comeback.
Or change this to synchronous call as you want to get the results immediately.
Can you help me how can I get user info.
NSString *name;
[FBRequestConnection startForMeWithCompletionHandler:^(FBRequestConnection *connection, id result, NSError *error)
{
if (!error)
{
// Success! Include your code to handle the results here
name = [result objectForKey:#"first_name"]; // Error!!! how to get data from this handler
}
else
{
// An error occurred, we need to handle the error
// See: https://developers.facebook.com/docs/ios/errors
}
}];
Code described above - asynchronous? How to make it synchronous? Tell me about the mechanism or tell me where to read. Thank you!
You could read everything about Facebook SDK on this site: https://developers.facebook.com.
They don't provide synchronous API and I don't even know why you might need it. But if you really do you can do some workaround. See implementation:
__block id result = nil;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[FBRequestConnection startForMeWithCompletionHandler:^(FBRequestConnection *connection, id theResult, NSError *error) {
result = theResult;
dispatch_semaphore_signal(semaphore); // UPDATE: dispatch_semaphore_wait call was here, which is wrong
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(#"%#", result);// this code will be launched after you've received response from Facebook
Result conforms FBGraphUser protocol. So if you it doesn't contain value for first_name key, user wouldn't have specified it. You could print result in debugger and see what it is.
Seems like you totally mess up. Try this code, but be sure, your understand it:
- (void) SetUserData
{
[FBRequestConnection startForMeWithCompletionHandler:^(FBRequestConnection *connection, id result, NSError *error)
{
if (!error)
{
self.first_name = [result objectForKey:#"first_name"];
NSLog(#"First name just came from server: first_name: %#", self.first_name);
}
else
{
}
}];
[self furtherProcessMethod];
}
- (void) furtherProcessMethod
{
if (self.first_name == nil)
{
// this is not recursion, do not miss that!!
[self performSelector:#selector(furtherProcessMethod) withObject:nil afterDelay:2.0];
}
else
{
NSLog(#"First name that I can work with first_name: %#", self.first_name);
// here you can handle you first_name, after it came
}
}
So after some time you need to get in log:
First name just came from server: first_name: some name
First name that I can work with first_name: some name
You can use additional method to solve your problem:
1) make name as property: __block NSString *name
2) move you code, that you need to perform, to separate method, like that:
- (void) methodWithComplitionHandler
{
name = nil;
[FBRequestConnection startForMeWithCompletionHandler:^(FBRequestConnection *connection, id result, NSError *error)
{
if (!error)
{
name = [result objectForKey:#"first_name"];
}
else
{
}
}];
[self futherProcessMethod];
}
- (void) furtherProcessMethod
{
if (self.name == nil)
{
[self performSelector:#selector(furtherProcessMethod) withObject:nil afterDelay:3.0]; // here set appropriate delay
}
else
{
// do some with name;
}
}
As I clarify in previous answer, you need to work with name inside "else" statement. Please, debug for more understanding. So we have:
- (void) SetUserData
{
[FBRequestConnection startForMeWithCompletionHandler:^(FBRequestConnection *connection, id result, NSError *error)
{
if (!error)
{
self.first_name = [result objectForKey:#"first_name"];
}
else
{
}
}];
[self furtherProcessMethod];
}
- (void) furtherProcessMethod
{
if (self.first_name == nil)
{
[self performSelector:#selector(furtherProcessMethod) withObject:nil afterDelay:30.0]; // here set appropriate delay
}
else
{
NSLog(#"first_name: %#", self.first_name);
}
}
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.