I use parse.com as backend, and my query was very slow, so I am trying to change it to use blocks.
Basically, my query populates an array with everything I need, and according to if statements, I'm calling methods inside the block, these methods populate the array that I will use in cellForRowAtIndexPath. The problem is that when I try to reloadDatainside block, the app crashes.
Here is the code:
- (void)queryForTable {
PFQuery *exerciciosQuery = [PFQuery queryWithClassName:#"ExerciciosPeso"];
[exerciciosQuery whereKey:#"usuario" equalTo:[PFUser currentUser]];
[exerciciosQuery includeKey:#"exercicio"];
exerciciosQuery.cachePolicy = kPFCachePolicyCacheElseNetwork;
[exerciciosQuery findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
[self configurarDatas];
_seriesArray = objects;
if (_seriesArray.count > 0) {
NSPredicate *predIniciante = [NSPredicate predicateWithFormat:#"serie contains [cd] %#", #"Ini"];
NSArray *arrayIniciante = [_seriesArray filteredArrayUsingPredicate:predIniciante];
NSArray *arrayInicianteApenasSeries = arrayIniciante;
NSArray *arrayInicianteApenasSeries2 = [arrayInicianteApenasSeries valueForKey:#"serie"];
NSSet *setInicianteApenasSeries = [NSSet setWithArray:arrayInicianteApenasSeries2];
NSArray *arrayInicianteCount = [setInicianteApenasSeries allObjects];
if (arrayInicianteCount.count > 0) {
[self popularSeriesInicianteAB];
// [self.tableView reloadData];
}
else if (arrayInicianteCount.count > 8) {
[self popularSeriesInicianteC];
// [self.tableView reloadData];
}
else {
[self popularSeriesAvancado];
// [self.tableView reloadData];
NSLog(#"POPULAR SERIES AVANÇADO");
}
}
}];
}
I have also tried to reloadData using dispatch_async(dispatch_get_main_queue(), ^{ [myDisplayedTable reloadData]; });, but it didn't work as well.
Anyway, I imagine that if I erase my methods and put everything inside the block, it will work, but I don't want to do that, as calling methods using if make my code easier to follow.
UPDATE:
For completeness, here are my DataSource methods:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSLog(#"numberOfRowsInSection %li", (unsigned long)_seriesForDisplay.count);
return _seriesForDisplay.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath object:(PFObject *)object {
static NSString *CellIdentifier = #"Cell";
PFTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[PFTableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
PFObject *o = _seriesForDisplay[indexPath.row];
// Texto da célula
cell.textLabel.text = o[#"serieDisplay"];
cell.detailTextLabel.text = o[#"grupos"];
return cell;
}
There are 2 way to implement data in tableview with Parse SDK
PFqueryTableViewController: you need implement
- (PFQuery *)queryForTable {
return query;
}
- (PFObject *)objectAtIndex:(NSIndexPath *)indexPath {
// overridden, since we want to implement sections
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:
(NSIndexPath *)indexPath object:(PFObject *)object {
//get data: object
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForNextPageAtIndexPath:
(NSIndexPath *)indexPath
UITableViewController/ UITableview in UIViewController:
This is normal way, implement uitableview delegate/datasource, you can write a method like (void)queryForTable and call reloadData in here.
I was using PFQueryTableViewController, which is from parse.com backend and has some different methods. I changed my query to void and used findObjectsInBackgroundWithBlock. Therefore, my method - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath object:(PFObject *)object, was not receiving the PFObjects, that's why I was getting a crash when tried to reloadData.
I changed my table back to UITableViewController.
I'm using dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
}); inside block and it's working now.
Great answers to this post, helped me a lot.
If you can't guarantee what thread a block will be run on (if it can run on a background thread) then you should switch to the main thread before actioning any UI changes. This could be done with performSelectorOnMainThread:withObject:waitUntilDone: or dispatch_async(dispatch_get_main_queue(), ^{.
Related
I'm building an app that retrieves items from a Parse database and inserts them into a UITableView. In my Parse database, each item has its own date (i.e. film dates). Now this is the part I’m having trouble with:
I would like to sort the items by their own date, with the date for that item being in the section header. I would also like to show only the items whose date is from the current date and onwards. Please also know that I’m using the latest version of iOS and using Objective-C. I have exhausted other sources but have yet to find exactly what I need. Any guidance will be greatly appreciated!
Right now I have this in my ShowsTableViewController.m:
My getShows method:
-(void)getShows{
PFQuery *retrieveShows = [PFQuery queryWithClassName:#"shows"];
[retrieveShows findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError
*error) {
//NSLog(#"%#", objects);
if (!error)
{
_showsArray = [[NSArray alloc] initWithArray:objects];
}
[showsTableView reloadData];
}];
}
And this is how I'm currently populating the cell:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
ShowsTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"showsCell" forIndexPath:indexPath];
PFObject *tempObject = [_showsArray objectAtIndex:indexPath.row];
cell.cellTitle.text = [tempObject objectForKey:#"title];
return cell;
}
With you require, I think you have to sort your data and put it in to a Array. And this Array will hold list of Dictionary. Each Dictionary will have two key (first key is title: and value of it is title you want show to header, second key is data: and value of it is array item in seciton)
And now you will implement UITableViewDataSource for it:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return [self.listData count];
}
for each section you can implement same like this:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSArray *data = [[self.listData objectAtIndex:section] objectForKey:#"data"];
}
And show header:
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
return [[self.listData objectAtIndex:section] objectForKey:#"title"];
}
And show to cell:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
ShowsTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"showsCell" forIndexPath:indexPath];
PFObject *tempObject = [[self.listData objectAtIndex:indexPath.section] objectAtIndex:indexPath.row];
cell.cellTitle.text = [tempObject objectForKey:#"title];
return cell;
}
You can do this way to achieve what you want.
I am working with parse on a little app. It should show names, where stored on parse user class in an Array column.
Here is my Code:
#synthesize friendsList;
-(void)viewDidLoad{
[super viewDidLoad];
PFQuery*query = [PFUser query];
[query getObjectInBackgroundWithId:#"myObjectId" block:^(PFObject *gameScore, NSError *error) {
if(error==nil){
friendsList = [gameScore valueForKey:#"Friends"];
NSLog(#"%#", friendsList);
}
else{
NSLog(#"Error");
}
}];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [friendsList count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"MainCell" forIndexPath:indexPath];
cell.textLabel.text = friendsList[indexPath.row];
return cell;
}
The problem is, when I am loading the data from parse. It does't show anything on the table view.
Do I declare friendsList likt this:
friendsList = [NSMutableArray arrayWithObjects:#"Name1", #"Name2", nil];
It will work.
Your tableView is blank as your friendsList is not populated when the tableView is displayed.
In the completion block reload the tableView.
In your completion block after friendsList = [gameScore valueForKey:#"Friends"]; statement add following code:
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData]; // assuming your table view is tableView
});
Hope this helps!
I think you missed allocating the array
friendsList = [[NSMutableArray alloc] init];
I'm working on a project and I want the user to be able to search a list on a UITableView. I've added the search bar and connected it to the search display controller. I have no idea how to actually search the list and grab the data from parse, filter it, set it to the PFUser of toUser, and display it. Anyone know how to do this?
Here's the code where I save the message:
if (toUser!= nil){
app[#"toUser"]=toUser;
}
[app save];
Here are some code snippets for the table view:
This displays the users and filters out the current user:
ParseExampleAppDelegate *delegate=[[UIApplication sharedApplication] delegate];
PFQuery *query = [PFUser query];
users = [[query findObjects] mutableCopy];
for(PFUser *user in users){
if ([user.objectId isEqualToString:delegate.applicationUser.objectId]){
[users removeObject:user];
break;
}
}
[users removeObject:delegate.applicationUser];
NSLog(#"all user: %lu",users.count);
[self.usersTable setDelegate:self];
[self.usersTable setDataSource:self];
[self.usersTable reloadData];
[self.usersTable reloadData];
Some methods for the table view:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"userCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
PFObject *tempObject = [users objectAtIndex:indexPath.row];
cell.text= [tempObject objectForKey:#"username"];
return cell;
NSMutableArray *filteredStrings;
BOOL isFiltered;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSLog(#"users:%lu",[users count]);
return [users count];
}
Thanks for your help,
armanb21
I created UITableView inside UIView (UserListVC) to display user data stored in a column typed array on Parse.com. The column called "followers" contains the array of PFUser objectId who did like the user in a row (in this case is current user).
In userListVC.m:
#implementation UserListViewController
{
NSMutableArray *tableData;
}
- (void)viewDidLoad {
[super viewDidLoad];
/*
tableData = [NSMutableArray arrayWithObjects:#"user1", #"user2", nil];
NSLog(#"tableData --> %#", tableData);
*/
tableData = [[NSMutableArray alloc] init];
PFQuery *userQuery = [PFQuery queryWithClassName:kPAWParseUserClassKey];
[userQuery whereKey:kPAWParseUsernameKey equalTo:[PFUser currentUser].username];
userQuery.cachePolicy = kPFCachePolicyNetworkElseCache;
[userQuery findObjectsInBackgroundWithBlock:^(NSArray *users, NSError *error)
{
if( !error )
{
NSArray *array = [users valueForKey:#"followings"];
for (int i = 0; i <= array.count; i++)
PFQuery *followingsQuery = [PFUser query];
[followingsQuery whereKey:#"objectId" equalTo:[[[users valueForKey:#"followings"] objectAtIndex:0] objectAtIndex:i]];
followingsQuery.cachePolicy = kPFCachePolicyNetworkElseCache;
[followingsQuery findObjectsInBackgroundWithBlock:^(NSArray *followings, NSError *error) {
if (!error) {
NSLog(#"following names --> %#", [followings valueForKey:kPAWParseUsernameKey]);
[tableData addObject:[followings valueForKey:kPAWParseUsernameKey]]; //??
[self.tableView reloadData];
NSLog(#"table data --> %#", tableData);
}
}];
}
}];
}
I am now able to extract usernames from the user class by using data array that I get from "followers" column and I also have [self.tableView reloadData]; in async query block but there is still a problem with tableview not showing usernames obtained from tableData. If I use sample data ("user1","user2") just for testing, there is no problem.
Here below I show my log from the code:
2014-12-31 11:05:00.626 Test[12354:60b] following names --> (
user1name
)
2014-12-31 11:05:00.628 Test[12354:60b] table data --> (
(
user1name
)
)
2014-12-31 11:05:00.631 Test[12354:60b] following names --> (
user2name
)
2014-12-31 11:05:00.633 Test[12354:60b] table data --> (
(
user1name
),
(
user2name
)
)
I think it is better to also provide code for the method (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath and here it is:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *tableIdentifier = #"TableItem";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:tableIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:tableIdentifier];
}
NSLog(#"table data xxxxx --> %#", tableData);
cell.textLabel.text = [tableData objectAtIndex:indexPath.row];
return cell;
}
The above method NEVER runs but if I test with my sample data, it does run. To be more specific, my questions would be:
This is UIView with UITableView inside it. I am not sure how to do [self.tableView reloadData]; properly. I mean how to declare tableView. In this case I do this:
#interface UserListViewController ()
#property (weak, nonatomic) UITableView *tableView;//????
#end
Why the method (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath never get run?
Just to answer these 2 specific questions:
Connect tableView to IBOutlet in .h file and synthesise it in .m file, then:
[self.tableView reloadData];
do the above inside async block.
The method (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath will get run if do as the above suggests.
However there remains other issues but they are out of the scope of this question I believe.
I have a problem with my TableViewController. There is a custom cell, with a class, and various infos dynamically loaded. My TableViewController appears, but my cell doesn't display, but i can touch this, and my transition with infos are good.
Thanks for your answers.
TableViewController.m
#interface Chat() {
NSMutableArray *messages;
UIRefreshControl *refreshControl;
}
#property (strong, nonatomic) IBOutlet UITableView *tableMessages;
#end
#implementation Chat
NSString *cellIdentifier = #"ChatCell";
- (void)viewDidLoad {
[super viewDidLoad];
[_tableMessages registerClass:[ChatCell class] forCellReuseIdentifier:cellIdentifier];
refreshControl = [[UIRefreshControl alloc] init];
[refreshControl addTarget:self action:#selector(loadMessages) forControlEvents:UIControlEventValueChanged];
[_tableMessages addSubview:refreshControl];
messages = [[NSMutableArray alloc] init];
[self loadMessages];
}
- (void)loadMessages {
if ([PFUser currentUser] != nil)
{
PFQuery *query = [PFQuery queryWithClassName:PF_MESSAGES_CLASS_NAME];
[query whereKey:PF_MESSAGES_USER equalTo:[PFUser currentUser]];
[query includeKey:PF_MESSAGES_LASTUSER];
[query orderByDescending:PF_MESSAGES_UPDATEDACTION];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (error == nil) {
[messages removeAllObjects];
[messages addObjectsFromArray:objects];
[_tableMessages reloadData];
} else [ProgressHUD showError:#"Network error."];
[refreshControl endRefreshing];
}];
}
}
- (void)actionCleanup {
[messages removeAllObjects];
[_tableMessages reloadData];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [messages count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
ChatCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
[cell bindData:messages[indexPath.row]];
return cell;
}
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
return YES;
}
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
DeleteMessageItem(messages[indexPath.row]);
[messages removeObjectAtIndex:indexPath.row];
[_tableMessages deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
PFObject *message = messages[indexPath.row];
ChatView *chatView = [[ChatView alloc] initWith:message[PF_MESSAGES_ROOMID]];
[self.navigationController pushViewController:chatView animated:YES];
}
#end
TableViewCell.m
#interface ChatCell() {
PFObject *message;
}
#end
#implementation ChatCell
- (void)bindData:(PFObject *)message_ {
message = message_;
_chatImg.layer.cornerRadius = _chatImg.frame.size.width/2;
_chatImg.layer.masksToBounds = YES;
PFUser *lastUser = message[PF_MESSAGES_LASTUSER];
[_chatImg setFile:lastUser[PF_USER_PICTURE]];
[_chatImg loadInBackground];
_chatUsername.text = message[PF_MESSAGES_DESCRIPTION];
_chatMessage.text = message[PF_MESSAGES_LASTMESSAGE];
NSTimeInterval seconds = [[NSDate date] timeIntervalSinceDate:message.updatedAt];
_chatDate.text = TimeElapsed(seconds);
}
#end
It's because you register the cell using - registerClass:forCellReuseIdentifier:.
If you register it this way you have to construct the view programmatically or load the nib file in ChatCell code.
To solve the problem, do either of these:
Create a nib file containing the view for your table view cell and set the class to ChatCell. Then use - registerNib:forCellReuseIdentifier: to register the nib.
Construct the view programmatically eg. create a UILabel and add it as a subview of ChatCell.
Make the prototype cell in the storyboard and set the cell identifier to ChatCell. Then remove the - registerClass:forCellReuseIdentifier:
Check You are given correct cell Identifier in storyboard. (case sensitive) " ChatCell"
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellIdentifier = #"ChatCell";
ChatCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
[cell bindData:messages[indexPath.row]];
return cell;
}
You are updating the UI on background thread. Try this, in your "loadMessages" method.
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
//update UI here
if (error == nil) {
[messages removeAllObjects];
[messages addObjectsFromArray:objects];
[_tableMessages reloadData];
} else [ProgressHUD showError:#"Network error."];
[refreshControl endRefreshing];
});
}];