Question

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.

Was it helpful?

Solution 2

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

OTHER TIPS

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).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top