Parse specific, how to save query results on an NSArray - ios

Im very new to iOS and PFQuery and I need your help please
IM trying to store the array of objects obtained form PFQuery into a local NSArray, Im trying to do it inside if (!error) but it does not leave the block, once the block terminates so does the values for it on my array.
//InstallersDirectory.m
#interface InstallersDirectoryTVC ()
#property (nonatomic, strong) NSArray *supervisors;
#end
//more code goes here
- (void)viewDidLoad
{
[super viewDidLoad];
PFQuery *query = [PFQuery queryWithClassName:#"InstallersInfo"];
[query whereKey:#"supervisor" equalTo:#"yes"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
for (PFObject *object in objects) {
self.supervisors = [objects valueForKey:#"supervisor"];
}
}else {
NSLog(#"Error, %# %#",error,[error userInfo]);
}
}];
Everything works inside the block, like self.supervisors.count or NSLog, but it doesn't leave the block at all. Could you please tell me how I can get those values store definitely to self.supervisors?
Thanks !!
edit | comment

If you still need help on this issue, here is a suggestion or two:
first, self.supervisors is a NSArray. As a NSArray it has to be fully initialized and populated at creation time. So, even though you are iterating through your results, you are creating a new array each time which means only the last iteration would likely be stored in self.supervisors.
try this instead,
#property (nonatomic,strong) NSMutableArray *supervisors;
in your block:
[self.supervisors addObject:object];
//note: this will put the entire returned object in your mutable array
then later outside your block:
to list all the supervisors:
for (PFObject *supervisor in self.supervisors) {
NSLog(#"supervisor info:%#",supervisor);
}
To get the 4th supervisor in the list:
PFObject *superVisorNumber4 = [self.supervisors objectAtIndex:4];
To get the phone number from that supervisor (making this up :)
NSString *phone = [superVisorNumber4 objectForKey:#"phone"];
hope that helps

Related

Parse.com returns empty objects

I have an NSArray called "malls" that contains a large number of NSDictionaries (each a specific mall) that I uploaded to Parse.com. I want my users to be able to access this information to create map annotations.
I've tried to do this in 2 different ways:
I tried uploading the entire array as a property of a single object:
this is the upload:
in the dataBank.h file:
#property (strong, nonatomic) NSMutableArray* malls;
in the .m file
PFObject *obj = [PFObject objectWithClassName:#"malls"];
obj[#"mallsData"] = self.malls;
[obj saveInBackground];
I try to get the data from parse:
-(NSMutableArray *)createAnnotationsFromParse
{
__block NSMutableArray* data = [[NSMutableArray alloc]init];
__block NSMutableArray* annots = [[NSMutableArray alloc]init];
PFQuery* query = [PFQuery queryWithClassName:#"malls"];
[query getObjectInBackgroundWithId:#"Eaib9yfTRe" block:^(PFObject *object, NSError *error) {
data = [object objectForKey:#"mallsData"];
annots = [self createAnnotations:data];
}];
return annots;
}
The problem is getObjectInBackground is asynchronous and always returns before getting the data from the server. I tried moving the "return annots" inside the code block but that gives the following error: "incompatible block pointer types".
I uploaded 5 "mall" objects to class "malls2". Each object has 2 properties- name and address:
for(int i = 0; i < 5; i++)
{
PFObject *mallsObj = [PFObject objectWithClassName:#"malls2"];
mallsObj[name] = [[self.malls objectAtIndex:i]objectForKey:name];
mallsObj[address] = [[self.malls objectAtIndex:i]objectForKey:address];
[mallsObj saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if(succeeded)
NSLog(#"yay");
else
NSLog(#"%#", error.description);
}];
}
then I try to get it back:
-(NSMutableArray *)createAnnotationsFromParse
{
__block Annotation* anno = [[Annotation alloc]init];
__block NSMutableArray* annots = [[NSMutableArray alloc]init];
PFQuery* query = [PFQuery queryWithClassName:#"malls2"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if(error)
NSLog(#"%#", error.description);
else
{
for(int i = 0; i < [objects count]; i++)
{
//createAnnotationWithTitle is a func in a different class that creates the annotation
anno = [anno createAnnotationWithTitle:[[objects objectAtIndex:i] objectForKey:name] andAddress:[[objects objectAtIndex:i]objectForKey:address]];
}
[annots addObject:anno];
}
}];
return annots;
}
I get 5 objects but they're all empty.
It's a basic misunderstanding about asynchronous methods with block parameters. The trick is to get out of the habit of thinking that code that appears later in a source file runs later. The assumption works in this function:
- (void)regularFunction {
// these NSLogs run top to bottom
NSLog(#"first");
NSLog(#"second");
NSLog(#"third");
}
This will generate logs: first, second, third. Top to bottom, but not in this one:
- (void)functionThatMakesAsynchCall {
// these NSLogs do not run top to bottom
NSLog(#"first");
[someObject doSomeAsynchThing:^{
NSLog(#"second");
}];
NSLog(#"third");
}
That function will generate logs - first, third, second. The "second" NSLog will run well after the "third" one.
So what should you do? Don't try to update the UI with results of a parse call until after it completes, like this:
// declared void because we can't return anything useful
- (void)doSomeParseThing {
// if you change the UI here, change it to say: "we're busy calling parse"
PFQuery* query = [PFQuery queryWithClassName:#"malls2"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if(!error) {
// change the UI here, say by setting the datasource to a UITableView
// equal to the objects block parameter
}
}];
// don't bother changing the UI here
// don't bother returning anything here
// we just started the request
}
But what if doSomeParseThing is really a model function, whose only job is to fetch from parse, not to know anything about UI? That's a very reasonable idea. To solve it, you need to build your model method the way parse built their's, with block parameter:
// in MyModel.m
// declared void because we can't return anything useful
+ (void)doSomeParseThing:(void (^)(NSArray *, NSError *))block {
PFQuery* query = [PFQuery queryWithClassName:#"malls2"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
block(objects, error);
}];
}
Now your view controller can call, leave the query work to your model and the UI work to the vc:
// change UI to 'busy' here
[MyModel doSomeParseThing:^(NSArray *objects, NSError *error) {
// update UI with objects or error here
}];
Figured it out. It looked like I was getting "empty objects" (can be seen here postimg.org/image/ot7ehn29b ) but once I tried to access data from the objects I saw there was no problem. Basiclly I was tricked by the PFObjects in the array showing "0 objects" and assumed it meant they came back from Parse.com empty. Here's how I checked, just for reference:
PFQuery *query = [PFQuery queryWithClassName:#"malls2"];
NSArray *array = [query findObjects];
NSLog(#"%#", [[array objectAtIndex:0] objectForKey:#"name"]; // I have a string property called "name" in my Parse object.

View Parse Data on WatchKit Table

I am trying to get my app working on WatchKit. I use Parse, and want to simply show the value of "Request" in my PFObject as a row in a WatchKit Table. I have the table and row set up and the row NSObject class simply has 1 label in it, where the "Request" field will populate. Here is what I have in my InterfaceController for the Watch.
#import "InterfaceController.h"
#import "TheTable.h"
#import <Parse/Parse.h>
#import "BlogView.h"
#interface InterfaceController()
#property (nonatomic, retain) PFObject *theObject;
#property (nonatomic, retain) NSMutableArray *rowTypesList;
#end
#implementation InterfaceController
- (void)awakeWithContext:(id)context {
[Parse setApplicationId:#"MYID"
clientKey:#"MYKEY"];
[super awakeWithContext:context];
// Configure interface objects here.
}
- (void)willActivate
{
PFQuery *query = [PFQuery queryWithClassName:#"Prayers"];
// If no objects are loaded in memory, we look to the cache first to fill the table
// and then subsequently do a query against the network.
[query orderByDescending:#"createdAt"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
NSArray *array = [objects objectAtIndex:0];//Selects the first "object" from all the "objects"
array = [array valueForKey:#"Request"];//Makes a NSArray from the "pairs" values
_rowTypesList = [array mutableCopy];//Converts the array to a NSMutableArray
NSLog(#"%#", _rowTypesList);
} else {
// Log details of the failure
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}]; [self setupTable];
}
- (void)setupTable
{
NSLog(#"%#", _rowTypesList);
[_theActualTable setRowTypes:_rowTypesList];
for (NSInteger i = 0;_theActualTable.numberOfRows; i++)
{
NSObject *row = [_theActualTable rowControllerAtIndex:i];
TheTable *importantRow = (TheTable *) row;
[importantRow.textRowLabel setText:???]; //THIS IS PROBLEM AREA, HOW DO I GET TEXT HERE FROM THE MUTABLEARRAY
}
}
#end
How do I get the value from the array for that row into the label?
I don't understand Objective C that much, but I get an idea.
You need to either call setupTable from the ParseQuery callback and pass it the results array, or either iterate that array in the callback and in each iteration call mytable.label.setText(array[i]["property"])
Hope it makes sense.

Querying a column in Parse

I have an iOS app that allows users to save GeoPoints to a Parse.com database table. Its a Google Maps app that lets users submit Lat/Long coordinates that will be displayed on the map once the Points have been approved by an admin type person.
I created a table called Point on Parse.com to save this information and the table has the following fields in addition the default ones that are created for you:
approved (String)
point (GeoPoints)
When a new row in the table is created (new Point is submitted by a user), "approved" is always set to "0" and "point" will contain the Lat/Long of a point that has been uploaded.
Once the "point" has been approved by an admin, "approved" is set to "1".
I query the table to display all the GeoPoints that have been approved (set to "1").
I can get all the rows that has been set to "1" but I dont know how to access the "point" field. I need to get the "point" for each row to display it to a map.
Here is what I have to query the table for all Points where "approved" is set to "1"
// Query the Database to get Points
PFQuery *query = [PFQuery queryWithClassName:#"Point"];
[query whereKey:#"approved" equalTo:#"0"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error)
{
if (!error)
{
// The find was successful
NSLog(#"Successfully Retrieved %d Number of Points", objects.count);
// Display the found Points as Markers
for (PFObject *object in objects)
{
NSLog(#"%#", object.objectId);
// Tried to do this but does not work
NSLog(#"%#", object.point);
}
}
else
{
// Show errors
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
I tried the following once I had an array of the objects but this does not work.
// Tried to do this but does not work
NSLog(#"%#", object.point);
Can anyone please help?
I think what you want is:
PFGeoPoint *point = object[#"point"];
Refer to the PFGeoPoint class reference for more information.
You can also go with subclassing for ease of use. Refer to iOS Guide Subclassing. Don't forget to register the subclass in your AppDelegate.
//Point.h
#import "Parse/Parse.h"
#interface Point : PFObject <PFSubclassing>
#property (nonatomic) BOOL approved;
#property (nonatomic, strong) PFGeoPoint *geoPoint;
+ (void)findApprovedPointsWithBlock:(PFArrayResultBlock)resultBlock;
#end
//Point.m
#import "Point.h"
#import <Parse/PFObject+Subclass.h>
#implementation Point
#dynamic approved;
#dynamic geoPoint;
+ (NSString *)parseClassName {
return #"Point";
}
+ (void)findApprovedPointsWithBlock:(PFArrayResultBlock)resultBlock {
PFQuery *query = [PFQuery queryWithClassName:#"Point"];
[query whereKey:#"approved" equalTo:#YES];
[query findObjectsInBackgroundWithBlock:^(NSArray *points, NSError *error) {
resultBlock(points, error);
}];
}
#end
// Somewhere in your code
[Point findApprovedPointsWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
for (Point *object in objects) {
NSLog(#"%#", object.objectId);
NSLog(#"%#", object.geoPoint);
}
}
else {
}
}];

PFQueryTableView trouble populating table view

I'm currently working on a PFQueryTableView and trying to get it to populate with data from an array that's pulled from ViewDidLoad. UPDATE: I've moved the function to an NSObject and implemented a singleton to be used across multiple classes in an effort to silo the operation away from the view controller. Below is the updated code:
+ (NSArray *)savedTankArray
{
PFUser *userName = [PFUser currentUser];
NSString *userNameString = [userName objectForKey:#"username"];
PFQuery *query = [[PFQuery alloc] initWithClassName:#"SavedTanks"];
[query whereKey:#"userName" equalTo:userNameString];
[query setValue:#"SavedTanks" forKeyPath:#"parseClassName"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error)
{
if (!error)
{
// The find succeeded.
NSLog(#"Successfully retrieved %lu Tanks.", objects.count);
// Do something with the found objects
for (PFObject *object in objects)
{
NSString *tankNameString = [[NSString alloc] init];
NSString *tankCapacityString = [[NSString alloc] init];
tankNameString = [object valueForKey:#"tankName"];
tankCapacityString = [object valueForKey:#"tankCapacity"];
NSLog(#"%#", tankNameString);
NSLog(#"%#", tankCapacityString);
_savedTankArray = [objects objectAtIndex:0];
}
}
else
{
// Log details of the failure
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
NSLog(#"TANK NAME ARRAY: %#", _savedTankArray);
return [_savedTankArray savedTankObjects];
}
While the NSLogs inside of the function work just fine, my problem is a bit expanded now, and I feel as though I'm missing something really simple here.
By the time I get to #"TANK NAME ARRAY: %#"... obviously it's returning null because its outside of the portion that handles the query. This doesn't help me much if I'm trying to bring the data in through another class.
I've tried so much over the past few days and I can't imagine I'm missing something terribly complex. I'm sorry for re-opening this but I can't wrap my head around it at this time.
Any ideas on how I could handle this? I appreciate the help as always.
There may be other trouble, but for sure this line:
tableData = [NSArray arrayWithObjects:objects, nil];
is a mistake. This will create a single-element array whose first element is the array of results. I think you can fix and simplify as:
tableData = objects;
For your question on how to proceed, I think you can carry on in this class the way one would in any table view controller. Answer the table datasource methods by referring to tableData (i.e. it's count for numberOfRowsInSection:, and tableData[indexPath.row] to configure a cellForRowAtIndexPath:, and so on).
New answer for the edited new question:
It appears that the mixup is with calling the asynch service. I'll give two kinds of advice here. First, the simplest possible table-containing view controller that gets its data from an asynch service, and second, a little class that wraps the parse asynch service. First the VC:
// in a vc with a table view .m
#interface MyViewController ()
#property(weak,nonatomic) IBOutlet UITableView *tableView;
#property(strong,nonatomic) NSArray *array; // this class keeps the array
#end
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[ClassThatHandlesMyQuery doQuery:^(NSArray *results) {
self.array = results;
[self.tableView reloadData];
}];
}
See how the query class method in the other class takes a block parameter? This is required because the query happens asynchronously.
// do the normal table view stuff
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.array.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
PFObject *pfObject = self.array[indexPath.row];
cell.textLabel.text = [pfObject valueForKey:#"someStringProperty"];
return cell;
}
That should be pretty much all you need in the vc. Now let's look at your query method. It makes three mistakes: (a) No block parameter to let the caller get the asynch result, (b) it mishandles the array in the query completion block, (c) at the end of the method, it wrongly supposes that a variable _savedTankArray is initialized, in the block. That code appears below the block, but it actually runs before the block runs.\
Let's fix all three problems. First declare a public method:
// ClassThatHandlesMyQuery.h
+ (void) doQuery:(void (^)(NSArray *))completion;
See how it takes a block as param? Now implement:
// ClassThatHandlesMyQuery.m
+ (void) doQuery:(void (^)(NSArray *))completion {
// your query code. let's assume this is fine
PFUser *userName = [PFUser currentUser];
NSString *userNameString = [userName objectForKey:#"username"];
PFQuery *query = [[PFQuery alloc] initWithClassName:#"SavedTanks"];
[query whereKey:#"userName" equalTo:userNameString];
[query setValue:#"SavedTanks" forKeyPath:#"parseClassName"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
// the job is MUCH simpler here than your code supposed.
// log the result for fun
NSLog(#"did we get objects? %#", objects);
// hand it back to the caller
// notice there's no array kept in this class. it's not needed
// and it would be awkward to do it at the class (not instance) level
completion(objects);
} else {
NSLog(#"bad news from parse: %#", error);
completion(nil);
}
}
// this is important
NSLog(#"hi mom!");
// watch your log output. 'hi mom' will appear before either message
// from the block. why is that? because that block runs later
// after the network request completes. but the hi mom NSLog runs
// just before the network request starts. this is why it's wrong to expect
// any variable set in the block to be initialized here
}
Believe it or not, that's it. You should be able to write exactly the mini view controller class and the mini query classes as described here, and see data from parse in a UITableView. I suggest you build something just like this (exactly like this) first just to get going

IBOutlet Collection Labels displaying one

I have a IBOutlet Collection view hooked up to 10 labels after pulling objects from a Parse query. My problem is that for some reason it logs 10 different object Ids but only displays one of the object Ids through the collection view. Here is the code I have:PFQuery *query =
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
// The find succeeded.
NSLog(#"Successfully retrieved %d scores.", objects.count);
// Do something with the found objects
int i = 0;
for (PFObject *object in objects) {
NSLog(#"%#", object.objectId);
for (UILabel *EventLabel in self.EventTitles){
(EventLabel *)self.EventTitles[i]= object.objectId;
i++;
}
}
Does anyone see a problem with the code for it to only display one rather than the 10?
The error is that you execute this cycle
for (UILabel *EventLabel in self.EventTitles){
EventLabel.text = object.objectId;
}
within this other cycle
for (PFObject *object in objects) {
}
It means that the first one gets hexecuted every time you get a new object from objects. And each time you get a object from objects you overwrite all the labels with the same objectID. The effect is that at end all the labels will show the objectID of the last object analysed. You should do something like the following instead:
int i = 0;
for (PFObject *object in objects) {
if (i >= [self.EventTitles count]) break;//to make sure we only write up to the max number of UILabels available in EventTitles
(UILabel *) self.EventTitles[i].text = object.objectId;//I assume the "objectId" property of object is an NSString!
i++;
}
You should rename "EventTitles" to "eventTitles" - it's a common rule that Class names start with a capital letter but instance variables ones don't. It will run anyway if you don't change it but it's a good think to do across your code.

Resources