I am trying to fetch a list of PFObjects of a PFUser to display in the iOS 8 Today Widget.
Following this blog post by Parse, I've enabled the same App Groups and Keychain Sharing in both my main app and extension in Xcode.
I've also enabled the following in the AppDelegate of my main app and the viewDidLoad of my Today Extension:
[Parse enableLocalDatastore];
[Parse enableDataSharingWithApplicationGroupIdentifier:#"group.com.me.myapp" containingApplication:#"com.me.myapp"];
[Parse setApplicationId:#"myAppId" clientKey:#"myClientId"];
In widgetPerformUpdateWithCompletionHandler, I constructed and performed my query:
- (void) widgetPerformUpdateWithCompletionHandler:(void (^)(NCUpdateResult))completionHandler {
PFQuery *query = [PFQuery queryWithClassName:#"Note"];
[query whereKey:#"User" equalTo:[PFUser currentUser]];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error)
{
// check for difference between current and new data
if([self hasNewData:objects]) {
// fresh data
notes = objects;
[self.tableView reloadData];
[self updatePreferredContentSize];
completionHandler(NCUpdateResultNewData);
} else {
// Data is the same
completionHandler(NCUpdateResultNoData);
}
} else {
// Failed
completionHandler(NCUpdateResultFailed);
}
}];
}
}
The first load seems to work fine - I'm able to get my list of PFObjects. However, whenever the extension reloads a second time, the following exception: enableDataSharingWithApplicationGroupIdentifier:containingApplication:' must be called before 'setApplicationId:clientKey'' is thrown at the enableDataSharingWithApplicationGroupIdentifier call in viewDidLoad.
I can replicate this reload by swiping the Notification Center to the "Notifications" tab and swiping it back, which would cause viewDidLoad to be invoked again.
I've double-checked that the order of calling the methods are right, and even tinkered with the order but am still getting the crash.
Any ideas? Thanks in advance!
Try this
if(![Parse isLocalDatastoreEnabled]) {
[Parse enableLocalDatastore];
[Parse enableDataSharingWithApplicationGroupIdentifier:#"group.com.me.myapp" containingApplication:#"com.me.myapp"];
[Parse setApplicationId:#"myAppId" clientKey:#"myClientId"];
}
Related
I'm trying to update an existing row in my Parse.com class from an iOS app. Upon doing so, I'm getting this error back from Parse:
This user is not allowed to perform the update operation on City. You can change this setting in the Data Browser. (Code: 119, Version: 1.9.1)
I have two Parse.com classes: City and Bar. The City class is read-only (it's a list of cities that won't change) and the Bar class is the class that users interact with (like adding comments, ratings, menu items, etc). Bar has a Pointer column on City and the permissions on Bar are all on for the public user.
Here's a snippet of code:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
PFQuery *query = [PFQuery queryWithClassName:#"Bar"];
self.bar = [query getObjectWithId:self.barId];
self.bar.name = #"Foo";
[self.bar saveInBackground];
}
Some notes about the snippet: I'm passing in the bar's ID from the previous view controller via segue. I also know this is running the Parse query on the main thread and it's going to block...I'm simply prototyping right now and this is meaningless code.
Upon running that snippet, I get the error listed above. As you can see in the code, I'm not changing the city value nor am I even including it in the query results. I don't understand why I would be getting this error. Any input would be greatly appreciated!
Try this code and see if the 2 NSLog messages appear in the order you expect:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
PFQuery *query = [PFQuery queryWithClassName:#"Bar"];
//self.bar = [query getObjectWithId:self.barId];
[query getObjectInBackgroundWithId:self.barId block:^(PFObject *result, NSError *error) {
NSLog(#"query has finished.");
self.bar = result[0];
}];
self.bar.name = #"Foo";
NSLog(#"Now going to saveInBackground.");
[self.bar saveInBackground];
}
And then try this, I think it may work, though I do not know all the details of your project:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
PFQuery *query = [PFQuery queryWithClassName:#"Bar"];
//self.bar = [query getObjectWithId:self.barId];
[query getObjectInBackgroundWithId:self.barId block:^(PFObject *result, NSError *error) {
self.bar = result[0]; // or result; ??
self.bar.name = #"Foo";
[self.bar saveInBackground];
}];
}
I have a PFQueryTableViewController table, which is getting info from parse database. When user select a row it goes to another VC. In that VC user delete some data in Parse table. This Parse table is the same table I have loaded my PFQueryTableViewController. Therefore, I do not need these deleted data to be shown.
When user press back button, this method in PFQueryTableViewController get called:
-(void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self.tableView reloadData];
[super objectsWillLoad];
}
I have checked line by line. It is reloading data but it is not calling:
- (PFQuery *)queryForTable {
PFQuery *query = [PFQuery queryWithClassName:#"MyClass"];
[query whereKey:#"user" equalTo:[PFUser currentUser]];
[query orderByDescending:#"createdAt"];
return query;
}
I deleted some data in the VC and dose not exist on the cloud. But when I press back button it exist. However, when I refresh the table, deleted data will be vanished!
I read documentation documentation but I can't find how I can call queryForTable method to connect to cloud and read data again.
You have to call this
[self loadObjects];
instead of
[self.tableView reloadData];
Working on an App that allow user to see what local people have posted (businesses, schools, or just people). I am able to post the information and save it to parse. I am also able to download user data near current location, but it issue is that my helper method does not do it right away when the app first launches.
I have tried to call the helper method below in the viewDidLoad,viewWillAppear and viewDidAppear and no luck. The postArray is always null when the user initially opens the application and goes to the map. If I move to another screen and come back to the mapVC the data is there...(I NSlog the postArray) and all the posts near the current location print out. I would like to have this data initially right when the map presents the user's location.
Questions.
Where should I call my helper method? When the app launches I want to
have the data so i can display it on the map.
Is there another method that I need to write?
Is there something wrong with the current method.
- (void)loadLocalPosts {
NSLog(#"Querying for Local Posts");
[activityIndicator startAnimating];
PFQuery *query = [PFQuery queryWithClassName:#"Post"];
[PFGeoPoint geoPointForCurrentLocationInBackground:^(PFGeoPoint *geoPoint, NSError
*error) {
geopoint = geoPoint;
[query whereKey:#"location" nearGeoPoint:geoPoint];
[query setLimit:50];
[query addDescendingOrder:#"createdAt"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
postArray = objects;
}];
}];
}
The problem here is that postArray is populated in a background thread and your application initializes faster than it can populate the array. This is common in asynchronous programming. The way to fix this, is by asking the map to refresh in the main thread.
- (void)loadLocalPosts {
NSLog(#"Querying for Local Posts");
[activityIndicator startAnimating];
PFQuery *query = [PFQuery queryWithClassName:#"Post"];
[PFGeoPoint geoPointForCurrentLocationInBackground:^(PFGeoPoint *geoPoint, NSError
*error) {
geopoint = geoPoint;
[query whereKey:#"location" nearGeoPoint:geoPoint];
[query setLimit:50];
[query addDescendingOrder:#"createdAt"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
postArray = objects;
dispatch_async(dispatch_get_main_queue(),^
{
// update view properties, refresh etc.
});
}];
}];
}
Note: All view related modifications should always happen in the main thread. Hence the dispatch_get_main_queue(). Now the main loop could however, be doing view related operations, hence a synchronous call will crash the application. Hence dispatch_async is used. This will add the block to the next run loop to be executed after the current one.
I am new with Parse and Ios-development.
I develop a ios-app that use Parse as backend.
I have got the main-function to work now, but i have a BIG problem.
I want to create a separate class for my API-handling to Parse. As i set it up now i have my parse-code directly in my view-controllers and as far as i know that not that nice coding.
But, the issue is to handle the background-jobs. Let say if i want to do a GET from the server, this can be done in a background-thread, just using "findObjectsInBackgroundWithBlock"
The problem is when i move this method to a separate API-class. Then my ViewController ask my API-class to get all the objects an the API-class will return it as soon its done. It will nor run in the background, i cant return a NSMutableArray with objects to the viewController until the fetch is done.
I have thinking that i maybe can get the data from parse synchronously in my API-class by using [query findObjects:&error] , as long as i figure out how to create my get-method in the API-class to run asynchronously.
I have try to create my API-method as a asynchronously method using blocks but will not run in background on a separate thread. (I am new to blocks an dont evan no if thats the correct way to crate a method that will run in a separate thread when using it)
Here is my API-method (Class: APIClient)
+ (void) GETAllShoppingGroups:(void (^) (NSMutableArray*))completionBlock{
//Create a mutable array (nil)
NSMutableArray *shoppingGroupsArray = nil;
//Create query for class ShoppingGroupe
PFQuery *query = [ShoppingGroupe query];
//Filter - find only the groups the current user is related to
[query whereKey:#"members" equalTo:[PFUser currentUser]];
//Sort Decending
[query orderByDescending:#"createdAt"];
//Tell Parse to also send the real member-objects and not only id
[query includeKey:#"members"];
//Send request of query to Parse with a "error-pointer"and fetch in a temp-array
NSError *error = nil;
NSArray *tempArray = [NSArray arrayWithArray:[query findObjects:&error]];
//Check for success
if (!tempArray) {
NSLog(#"%#", error);
NSLog(#"ERROR: %#", [error userInfo][#"error"]);
return completionBlock(shoppingGroupsArray);
} else {
//Seccess
shoppingGroupsArray = tempArray.mutableCopy;
completionBlock(shoppingGroupsArray);
}
}
Here is my ViewController Class (Class: ShoppingGruopViewController)
- (void) getAllObjects{
//Init array if nil
if (!self.shoppingGroupeArray) {
self.shoppingGroupeArray = [[NSMutableArray alloc]init];
}
//Remove old objects
[self.shoppingGroupeArray removeAllObjects];
//Get objects
[APIClient GETAllShoppingGroups:^(NSMutableArray* completionBlock){
if (completionBlock) {
[self.shoppingGroupeArray addObjectsFromArray:completionBlock]; }
[self.tableView reloadData];
}];
}
I use a query FindObjectsInBackgroundWithBlock (assync) to get a value from webservice... I want that value on the appdelegate before the app loads. I'm trying do it but he still load the app before check the webservices to get the value. How can I handle it? I already tried with a NSTimer..
I called this method in -(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions :
-(void)checkUserVersion{
PFQuery *query = [PFQuery queryWithClassName:#"sqliteversion"]; //1
// NSNumber *n=databaseVersion;
[query whereKey:#"user_version" equalTo:[NSNumber numberWithInt:[databaseVersion integerValue]]];//2
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {//4
if (!error && [objects count]>0) {
for (PFObject *object in objects) {
valor1=[[object objectForKey:#"Value"]intValue];
user_version=[NSNumber numberWithInt:valor1];
}
}
}];
}
You may want to evaluate why it's necessary to have this information from Parse.com before launch. Also, it can mean many things to say "before launch", and there are many solutions that don't involve doing "everything" before the first view is attached to the window and shown.
That said...run the PFQuery fetch in the foreground. Doing this will block execution of the rest of your code, though. Instead of findObjectsInBackground just use the findObjects method of PFQuery. I would advise against doing this though, and instead try to find out a way to wait for the query to return while the app is already loaded. Maybe you could suspend user interaction but display a loading indicator or something?
You can't run any code in your app before it finishes launching. If the value is critical for your app to begin working you'll have to show a "waiting for data" message.