I am trying to populate a PFQueryTableViewController with Question objects from my Parse backend. How do I implement blocks within this method?
- (PFQuery *)queryForTable // I'm having issues with using the method "findObjectInBackgroundWithBlock" in this method.
{
PFQuery *query = [PFQuery queryWithClassName:self.parseClassName];
[query fromLocalDatastore];
[query orderByDescending:#"createdAt"];
[query findObjectsInBackgroundWithBlock:^(NSArray *parseQuestions, NSError *error) { // Fetch from local datastore
if (parseQuestions != nil) {
NSMutableArray *mutableParseQuestions = [parseQuestions mutableCopy];
self.questions = mutableParseQuestions; // if Set local array to fetched Parse questions
} else {
if ([InternetReachabilityManager isReachable]) {
[query findObjectsInBackgroundWithBlock:^(NSArray *parseQuestions, NSError *error) { // Fetch from Cloud
NSMutableArray *mutableParseQuestions = [parseQuestions mutableCopy];
self.questions = mutableParseQuestions; // if Set local array to fetched Parse questions
[Question pinAllInBackground:parseQuestions]; // Save query results to local datastore
}];
}
}
}];
return query;
}
When blocks are in this method I get this error.
Is this because queryForTable is already a background process? Should I just use
[query findObjects]
Also, I'm trying to implement reachability into my fetch.
Try fetching from local datastore
Load data into table if they are there else switch to the network
Call block if network is available
Save the results of the network fetch into the local datastore
I know that this method is supposed to automatically assign objects it fetches to rows but I don't know how to work with it if we're passing around a PFObject subclass object. This is my explanation for the arrays.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
self.question = [self.questions objectAtIndex:indexPath.row]; // Save one question from row
static NSString *identifier = #"QuestionsCell";
QuestionsCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if (cell == nil) {
cell = [[QuestionsCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
}
[cell setQuestion:self.question]; //
return cell;
}
This is how I use the fetched array to populate the tableView.
So my questions:
Why can't I call a block inside of queryForTable?
Is there any way I can make this simpler by using queryForTable's automatic assigning of objects to rows?
If the internet is unreachable and the local datastore is empty what should I do?
There is no reason to call your query in that function. You're supposed to create a query and then return it. PFQueryTableViewController does the actual handling of the request. Read the documentation here: https://parse.com/docs/ios/api/Classes/PFQueryTableViewController.html#//api/name/queryForTable
Once you're in the cellForRowAtIndexPath method you can call objectAtIndexPath: which will you give you the object you need and then use the data from it to set up your cell.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
PFObject *object = [self objectAtIndexPath:indexPath];
UITableViewCell *cell = ....
//configure cell using object here
return cell;
}
You do this (semi-pseudo code)
-(NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section {
if (self.objects.count == 0) {
Put some sort of overlay that shows there are no objects to be retrieved or internet is unreachable.
}
return self.objects.count;
}
Related
I am working on a app that you can change when a item on the menu is in stock or out of stock.
I have it now so it changes the UISwitch to on or off when it loads the screen. I need each switch to change a NSString in parse that makes it one or zero.One meaning that it is on zero meaning its off.
I am fairly new to objective c and parse so if any one could help me get a start on this problem that would be great!
You might use something like that:
PFQuery *query = [PFQuery queryWithClassName:#"YourClass"];
[query whereKey:#"user" equalTo:[PFUser currentUser]];
[query getFirstObjectInBackgroundWithBlock:^(PFObject * yourClass, NSError *error) {
if (!error) {
// Found yourClass object
[yourClass setObject:isInStock forKey:#"isInStock"];
// Save
[yourClass saveInBackground];
} else {
// Did not find any yourClass object for the current user
NSLog(#"Error: %#", error);
}
}];
NSArray *listObjects = .... (loading from Server) // List of PFObject
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
PFObject *object = [listObjects objectAtIndex:indexPath.row];
YourCell *cell = .....
if ([[object valueForKey:#"sandwichesOutofstock"] intValue] == 1)
cell.switch.on = true;
else
cell.switch.on = false;
cell.switch.tag = 500 + index.row;
[cell.switch addTarget:self action:#selector(switchTouch:) forControlEvents:UIControlEventTouchUpInside]
.........
}
(IBAction)switchTouch:(UISwitch *)switch{
long index = switch.tag - 500;
PFObject *object = [listObjects objectAtIndex:index];
if(switch.on)
[object setValue:#"1" ForKey:#"sandwichesOutofstock"];
else{
[object setValue:#"0" ForKey:#"sandwichesOutofstock"];
}
[object saveInBackground];
[self.tableView reloadRowsAtIndexPaths:[NSIndexPath indexPathForRow:index inSection:0] withRowAnimation:UITableViewRowAnimationNone];
}
You could assign a reference of the PFObject to the cell. Then when the switch changes just get the cell's object and make the change.
I am trying to skip a row creation in my cellForRowAtIndexPath without empty cells in between while retrieving data from PFQueryTableViewController ? How can i create a cell when my condition satisfies and skip creating a blank cell ?
Here's my code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath object:(PFObject *)object {
//NSLog(#"data is %#",[object objectForKey:#"ItemName"]);
PFQuery *query = [PFQuery queryWithClassName:#"userData"];
[query whereKey:#"username" equalTo:[PFUser currentUser].username];
[query getFirstObjectInBackgroundWithBlock:^(PFObject * userData, NSError *error) {
if (!error) {
//Doing some logic to get result
if (result == a) {
static NSString *CellIdentifier = #"ident";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil){
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier];
}
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.textLabel.text = [object objectForKey:#"ItemName"];
cell.detailTextLabel.text = [object objectForKey:#"Price"];
cell.detailTextLabel.textColor = [UIColor blueColor];
[_itemPrefArray removeAllObjects];
[_userPrefArray removeAllObjects];
return cell;
}
}else {
NSLog(#"Error: error setting cell data %#", error);
}
}];
}
It is throwing compilation error as i am not aware of how to return a cell based on condition. I tried using id cell = nil; and tried returning which is not working.
So based on what I understand, you should query for the user data only once, most likely in viewWillLoad(), if that scenario fits. Once the query is successful, use the results of the query to filter your other query to determine which objects meet your predicate requirements and save those objects that fit your predicate to an NSMutableArray. This array corresponds to all the records that match what you're trying to do. This array corresponds to the number of cells in the tableview and then in cellForRowAtIndexPath: , to get the contents of the cell, just index the results array based on indexPath.row.
I tried to provide a trivial example to show you what I'm thinking of since I cannot provide any more specifics without knowing the other queries and the predicate you're using to find matches..
-(void)queryForUserData(){
PFQuery *query = [PFQuery queryWithClassName:#"userData"];
[query whereKey:#"username" equalTo:[PFUser currentUser].username];
[query getFirstObjectInBackgroundWithBlock:^(PFObject * userData, NSError *error) {
if(error == nil){
filterResultsUsingUserData(userData);
tableView.reloadData();
}
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath object:(PFObject *)object {
static NSString *CellIdentifier = #"ident";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil){
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier];
}
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
//Use the results from filterResultsUsingUserData to fill in contents
}
-(void)filterResultsUsingUserData(PFObject*)userData{
//Filter results based on a predicate you're comparing to and save to immutable property. Number of results corresponds to number of cells
for(int i = 0;i < objects.count;i++){
if(a == b){
results.addObject(objects[i]);
}
}
}
I have an object called masterMessages filled with objects called messages. Each message object has five keys:
objectId
senderId
senderName
messageBody
timestamp
Basically what I am doing now is querying all the messages sent to my user in this object called masterMessages. Then i'm using:
self.senderIds = [masterMessages valueForKeyPath:#"#distinctUnionOfObjects.senderId"];
to get all the different sender ids (senderId) in an array called senderIds. With this I will populate my conversations table. But i want to populate it with the sender names (senderName) and not the senderIds. I only do it this way in case two users have the same name.
I am trying to find:
How do I say "get valueForKey:#"senderName" for this senderId
and
is there a better way to populate my conversations table?
Here is my code:
note: im using parse.com
-(void)viewWillAppear:(BOOL)animated{
NSString *userId = [[PFUser currentUser] objectId];
PFQuery *query = [PFQuery queryWithClassName:#"lean"];
[query whereKey:#"recipientId" equalTo:userId];
[query orderByDescending:#"createdAt"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (error) {
NSLog(#"Error: %# %#", error, [error userInfo]);
}
else {
// We found messages!
masterMessages = objects;
NSLog(#"self.messages = %#", masterMessages);
self.senderIds = [masterMessages valueForKeyPath:#"#distinctUnionOfObjects.senderId"];
NSLog(#"self.senderIds = %#", self.senderIds);
[self.tableView reloadData];
}
}];
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
return [self.senderIds count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
NSLog(#"self.senderIds = %#", self.senderIds);
NSString *senderDisplayName = [self.senderIds objectAtIndex:indexPath.row];
NSLog(#"sender = %#", senderDisplayName);
cell.textLabel.text = senderDisplayName;
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
selectedId = [self.senderIds objectAtIndex:indexPath.row];
[self performSegueWithIdentifier:#"ShowMissionMessage" sender:self];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"ShowMissionMessage"]) {
[segue.destinationViewController setHidesBottomBarWhenPushed:YES];
MissionChat *missionchatviewcontroller = (MissionChat *)segue.destinationViewController;
missionchatviewcontroller.selectedId = selectedId;
missionchatviewcontroller.masterMessages = masterMessages;
}
}
There are a few issues in the question, one is how to dereference PFObjects, which we took care of on another thread. The rest of this question is about (a) how to use parse objects to build a datasource to support a tableview, and a harder one (b) how to get information from related objects.
Starting with (b), the harder one: There are a few ways to relate objects. Your choice a string-typed column containing the related object id, is intuitive (especially if you have an SQL background), but the least advisable. The better (best) way to model a one-to-one or small one-to-many relation is with a pointer (or array of pointers if one-to-many).
So I think your senderId and recipientId string columns should be replaced by pointer-typed columns called sender and recipient. The huge advantage of this is the ability to eagerly fetch those pointed-to objects on the message (or "lean" in your terms) query.
Having made that change, you're new improved query looks like this:
PFQuery *query = [PFQuery queryWithClassName:#"lean"];
// notice the first change for the better here:
[query whereKey:#"recipient" equalTo:[PFUser currentUser]];
[query orderByDescending:#"createdAt"];
// notice the really valuable feature here:
[query includeKey:#"sender"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
// using the array of PFObjects understanding from your other question...
for (PFObject *pfObject in objects) {
NSString *messageBody = [pfObject objectForKey:#"messageBody"];
// these lines here are the punch line:
PFUser *sender = [pfObject objectForKey:#"sender"];
NSString *senderName = [sender username];
NSLog(#"The message %# was sent by %#", messageBody, senderName);
}
}];
The important thing to notice above is that we were able to ask resulting objects for the #"sender" column, and, because you've changed it to a pointer, and because you've done an includeKey on the query, that complete object (e.g. including the PFUser username) is now fetched.
Now the easy question (a). Now that you have the data right from the server, the datasource for the table is nothing more than the returned objects. In other words, throw away the the senderIds array and replace it with:
#property(nonatomic, strong) NSArray *messages;
Your find block becomes trivial:
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
self.messages = objects;
}];
Answer messages.count for numberOfRowsInSection, and then pick what you need from the objects in cellForRowAtIndexPath...
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
PFObject *message = self.messages[indexPath.row];
NSString *messageBody = [message objectForKey:#"messageBody"];
PFUser *sender = [message objectForKey:#"sender"];
NSString *senderName = [sender username];
cell.textLabel.text = senderName;
return cell;
}
function findObjectByKey(array, key, value) {
for (var i = 0; i < array.length; i++) {
if (array[i][key] === value) {
return array[i];
}
}
return null;
}
var obj = findObjectByKey(objArray, 'id', 3);
//ES6
var obj = objArray.find(function (obj) { return obj.id === 3; });
function getObjects(obj, key, val) {
var objects = [];
for (var i in obj) {
if (!obj.hasOwnProperty(i)) continue;
if (typeof obj[i] == 'object') {
objects = objects.concat(getObjects(obj[i], key, val));
} else if (i == key && obj[key] == val) {
objects.push(obj);
}
}
return objects;
}
var result = getObjects(obj, 'category_id', valu.category_id);
console.log(result);
I'm currently developing my first App. The App is a Basic messenger App based on Parse.com.
I want to create a PFQueryTableViewController where it will show the recent chats with other users.
Photo of other user, Name of other user and timestamp (similar to Facebook messanger "recent" tab).
the chats data is saved in a Parse Class named Room with the following columns:
objectid(string)
roomname(string)
User_1(pointer to _User)
User_2(pointer to _User)...
I can fill the table view easily with the string values (e.g the roomname) but I would like to get the users #"full name" as the Label of each cell.
this is my code (I get an empty TableView):
- (PFQuery *)queryForTable {
PFQuery *query = [PFQuery queryWithClassName:#"Room"];
[query includeKey:#"User_2"];
if (self.objects.count == 0) {
query.cachePolicy = kPFCachePolicyCacheThenNetwork;
}
[query orderByDescending:#"createdAt"];
return query;
}
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
object:(PFObject *)object
{
static NSString *cellIdentifier = #"Cell";
PFTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (!cell) {
cell = [[PFTableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle
reuseIdentifier:cellIdentifier];
}
cell.textLabel.text = object[#"fullname"];
return cell;
}
When the pointer object is initially available it's just a stub, it doesn't actually contain any data. You need to call fetchAllIfNeededInBackground:block: on it (them, to keep things efficient) to populate the data.
Look at subclassing the table view controller and overriding - (void)objectsDidLoad:(NSError *)error to trigger the fetch on the new objects.
Note that you might just want to change your Room class to cache the user names (though if you do that you will need some cloud code to update the caches if the user name changes).
Ok, I solved it, hope this helps others.
This is the updated working code:
- (PFQuery *)queryForTable {
PFQuery *query = [PFQuery queryWithClassName:#"Room"];
[query includeKey:#"User_2"];
// 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.
if (self.objects.count == 0) {
query.cachePolicy = kPFCachePolicyCacheThenNetwork;
}
[query orderByDescending:#"createdAt"];
return query;
}
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
object:(PFObject *)object
{
static NSString *cellIdentifier = #"Cell";
PFTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (!cell) {
cell = [[PFTableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle
reuseIdentifier:cellIdentifier];
}
PFObject *user = object[#"User_2"];
[user fetchIfNeededInBackgroundWithBlock:^(PFObject *user, NSError *error) {
//NSLog(#"%#", user);
cell.textLabel.text = user[#"fullname"];
}];
return cell;
}
#end
This question has been successfully answered; thank you to jsksma2.
I cannot get my data to fill the rows in my TableView, even though I get the data back properly and can hard-code the tableview to display a static amount of dummy text. I have a hunch my issue relates to initWithStyle vs initWithCoder for subclassed UITableViewCells.
In a subclass of UITableViewController called "GiveItemsTableViewC", during viewDidLoad I am querying Parse for objects each called "PFGiveItem". I get these back and add each one to a global variable, a mutable array called "myGiveItems". I log these, and I get what I am looking for, so that part is working.
GiveItemsTableViewController
- (void)viewDidLoad
{
[super viewDidLoad];
PFQuery *query = [PFQuery queryWithClassName:#"giveItem"];
[query whereKey:#"giver" equalTo:[PFUser currentUser]];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
self.myGiveItems = [[NSMutableArray alloc]init];
for (PFObject *object in objects) {
PFGiveItem *newGiveItem = [[PFGiveItem alloc]init];
newGiveItem.giveItemName = object[#"giveItemTitle"];
newGiveItem.giveItemImage = object[#"giveItemPhoto"];
[self.myGiveItems addObject:newGiveItem];
}
} else {
// Log details of the failure
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
}
Now I am trying to load each one of these giveItems into a TableView object, using custom TableViewCells each called "GiveItemCell."
GiveItemCell.m
#implementation JFGiveItemCell
#synthesize giveItemImageView = _giveItemImageView;
#synthesize giveItemLabel = _giveItemLabel;
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Initialization code
}
return self;
}
Back in the table view controller, I return one section for the table view.
And when I include a static number for the rowsInSection, I can output test values to each cell. If I execute the code below, I will get a tableView with cells with the label of "Test", as per the upcoming cellForRowAtIndexPath method. So it works with that test, but obviously I'm looking to dynamically load the proper information.
GiveItemsTableViewController
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 4;
}
- (JFGiveItemCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellIdentifier = #"Cell";
JFGiveItemCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil){
cell = [[JFGiveItemCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier];
}
// PFGiveItem *giveItem = self.myGiveItems[indexPath.row];
// cell.giveItemLabel.text = giveItem.giveItemName;
cell.giveItemLabel.text = #"Test";
return cell;
}
It looks like you're forgetting to call [tableView reloadData] in the callback of your block method:
- (void)viewDidLoad
{
[super viewDidLoad];
PFQuery *query = [PFQuery queryWithClassName:#"giveItem"];
[query whereKey:#"giver" equalTo:[PFUser currentUser]];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
self.myGiveItems = [[NSMutableArray alloc]init];
for (PFObject *object in objects) {
PFGiveItem *newGiveItem = [[PFGiveItem alloc]init];
newGiveItem.giveItemName = object[#"giveItemTitle"];
newGiveItem.giveItemImage = object[#"giveItemPhoto"];
[self.myGiveItems addObject:newGiveItem];
}
[self.tableView reloadData];
} else {
// Log details of the failure
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
}
Also, I second #CrimsonChris in saying that you need to set your dataSource methods properly:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return self.myGiveItems.count
}
There are a couple of problems...
Your numberOfRowsInSection should return the size of your myGiveItems array.
You need to tell your table view to reload when you finish loading your items asynchronously.
You don't need to implement number of sections, it defaults to 1.