I am having trouble getting my UITableView to reload appropriately. My app sometime works and other times it crashes because one of the data elements is not properly stored in an array when the tableView tries to update. Can someone point me out the proper place to reload my tableView please.
- (void)viewDidLoad {
[super viewDidLoad];
[self queryForNewBooks];}
-(void)queryForNewBooks{
_bookNameArray = [[NSMutableArray alloc] init];
_authorNameArray = [[NSMutableArray alloc] init];
_isbnNumberArray = [[NSMutableArray alloc] init];
_bookImageData = [[NSMutableArray alloc] init];
PFQuery *query = [PFQuery queryWithClassName:#"BooksForSale"];
[query orderByDescending:#"createdAt"];
[query findObjectsInBackgroundWithBlock:^( NSArray *objects, NSError *error) {
if (objects.count >=1) {
for (PFObject *object in objects) {
PFFile *imageFile = [object objectForKey:#"image"];
[imageFile getDataInBackgroundWithBlock:^(NSData *result, NSError *error) {
if (!error) {
NSData *data = result;
NSLog(#"HEYYYYYY");
if (data == NULL) {
}
else{
[ _bookImageData addObject:data];
//I have tried placing it here [self.tableView reloadData]
// NSLog(#"%#",[_bookImageData objectAtIndex:0]);
}
}
}
];
NSDictionary *bookNameDictionary = object[#"nameOfBook"];
NSDictionary *authorNameDictionary = object[#"Author"];
NSDictionary *bookImageDictionary = object [#"image"];
NSDictionary *isbnNumberDictionary = object [#"isbnNumber"];
NSString *objectID = [object objectId];
if (bookNameDictionary != NULL){
NSLog(#"Yo bro here is the book name %#",bookNameDictionary);
[_bookNameArray addObject:bookNameDictionary];
NSLog(#"number: %#", bookNameDictionary);
}
if (bookNameDictionary == NULL) {
[_bookNameArray addObject:#""];
NSLog(#"Blank space");
}
if (authorNameDictionary != NULL) {
[_authorNameArray addObject:authorNameDictionary];
// [_tableData addObject:ft];
NSLog(#"Author Name : %#",_authorNameArray);
// NSLog(#"the table data is %#",_tableData);
}
if (authorNameDictionary == NULL) {
[_authorNameArray addObject:#""];
NSLog(#"Blank space");
}
if (isbnNumberDictionary != NULL){
NSLog(#"Yo bro here is the isbn %#",isbnNumberDictionary);
[_isbnNumberArray addObject:isbnNumberDictionary];
NSLog(#"number: %#", isbnNumberDictionary);
//[self.tableview reloadData];
}
if (isbnNumberDictionary == NULL) {
[_isbnNumberArray addObject:#""];
NSLog(#"Blank space");
}
/* if (bookImageDictionary !=NULL){
[_bookImageData addObject:bookImageDictionary];
}
if (bookImageDictionary ==NULL){
[_bookImageData addObject:#""];
NSLog(#"Blank Space");
}*/
if (objectID != NULL) {
[_objectIDArray addObject:objectID];
NSLog(#"object id is : %#",objectID);
}
if (objectID ==NULL){
[_objectIDArray addObject:#"blank"];
}
}
}
// code
}];
//I have tried placing it here [self.tableView reloadData]
);
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [self.bookNameArray count];
}
(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *simpleTableIdentifier = #"TableViewCell";
TableViewCell *cell = (TableViewCell *)[tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"TableViewCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
NSLog(#"Here are the book names, %#",_bookNameArray);
cell.bookNameLabel.text = [_bookNameArray objectAtIndex:indexPath.row];
cell.authorNameLabel.text = [_authorNameArray objectAtIndex:indexPath.row];
if ([_bookImageData objectAtIndex:indexPath.row] != NULL ) {
NSLog(#"it seems to work");
UIImage *image = [UIImage imageWithData: [_bookImageData objectAtIndex:indexPath.row]];
cell.bookImageLabel.image = image;
}
else{
NSLog(#"Error");
}
return cell;
}
Updating question:
Is this the proper way to declare a PFimageView?
Can I just drag a UIImageView in the Xib file and then
change its class to PFImageView?
After changing it to a PFImageView should I be able to just link the view to the outlet as normally done?
#import <UIKit/UIKit.h>
#import <Parse/Parse.h>
#import <ParseUI/ParseUI.h>
#interface TableViewCell : UITableViewCell
#property (nonatomic, weak) IBOutlet UILabel *bookNameLabel;
#property (nonatomic, weak) IBOutlet UILabel *authorNameLabel;
#property (nonatomic, weak) IBOutlet PFImageView *bookImageLabel;
#property (nonatomic, weak) IBOutlet UILabel *priceOfBook;
#end
#import <Parse/Parse.h>
#import "TableViewCell.h"
#import <ParseUI/ParseUI.h>
#implementation TableViewCell
#synthesize bookNameLabel =_bookNameLabel;
#synthesize authorNameLabel = _authorNameLabel;
#synthesize bookImageLabel = _bookImageLabel;
- (void)awakeFromNib {
// Initialization code
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
#end
The part that complicates the code is the need to fetch images using the objects returned by the query. The simplest solution is to omit the image fetching from the query completion handler.
Instead, loop the returned objects, building the arrays (there's room for improvement here, too, but just sticking with the crash part of your problem for now). In the _bookImageData array, don't try to keep images, instead keep the PFFile for each object...
// in the loop of objects, omit getDataInBackground and just do...
[_bookImageData addObject:object[#"image"]];
The answer to the stated question -- where to reload the table -- is after the loop that builds the table datasource.
for (PFObject *object in objects) {
// code to build all of the arrays, omitting getDataInBackground
// ...
}
[self.tableView reloadData];
In your table view cell, replace the image view with a PFImageView, because it can take care of fetching the image data for you (and other useful stuff like cacheing it). Your cellForRowAtIndexPath: will then look like this...
// bookImageLabel must be a PFImageView
// remember, we put the PFFile for each object into the _bookImageData array
cell.bookImageLabel.file = _bookImageData[indexPath.row];
[cell.bookImageLabel loadInBackground];
Related
How can I import all the contacts from the phone book in your application. and so that we could gain the imported contacts.
Andrei brother if you have not got answer still,I give you answer.I tried and got the solution.It works fine.
The AddressBookUI framework is deprecated in iOS 9, so we need to use Contact Framework.
First I set or hook up the tableView using XIB or Storyboard for showing contacts.
Must import the Contacts framework
ViewController.h
#import <UIKit/UIKit.h>
#import <Contacts/Contacts.h>
#interface ViewController : UIViewController<UITableViewDataSource,UITableViewDelegate>
{
}
#property (strong, nonatomic) IBOutlet UITableView *tableViewShowContacts;
#end
ViewController.m
#import "ViewController.h"
#interface ViewController ()
{
NSMutableArray *arrayContacts;
}
#end
#implementation ViewController
#synthesize tableViewShowContacts;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
arrayContacts = [[NSMutableArray alloc]init];
[self getAuthorizationandContact];
//Register the cell
[tableViewContactData registerClass:[UITableViewCell class] forCellReuseIdentifier:#"cell"];
[self.view addSubview:tableViewShowContacts];
}
-(void)fetchContactsandAuthorization
{
// Request authorization to Contacts
CNContactStore *store = [[CNContactStore alloc] init];
[store requestAccessForEntityType:CNEntityTypeContacts completionHandler:^(BOOL granted, NSError * _Nullable error) {
if (granted == YES)
{
//keys with fetching properties
NSArray *keys = #[CNContactFamilyNameKey, CNContactGivenNameKey, CNContactPhoneNumbersKey, CNContactImageDataKey];
NSString *containerId = store.defaultContainerIdentifier;
NSPredicate *predicate = [CNContact predicateForContactsInContainerWithIdentifier:containerId];
NSError *error;
NSArray *cnContacts = [store unifiedContactsMatchingPredicate:predicate keysToFetch:keys error:&error];
if (error) {
NSLog(#"error fetching contacts %#", error);
} else {
NSString *phone;
NSString *fullName;
NSString *firstName;
NSString *lastName;
UIImage *profileImage;
NSMutableArray *contactNumbersArray = [[NSMutableArray alloc]init];
for (CNContact *contact in cnContacts)
{
// copy data to my custom Contacts class.
firstName = contact.givenName;
lastName = contact.familyName;
if (lastName == nil) {
fullName=[NSString stringWithFormat:#"%#",firstName];
}else if (firstName == nil){
fullName=[NSString stringWithFormat:#"%#",lastName];
}
else{
fullName=[NSString stringWithFormat:#"%# %#",firstName,lastName];
}
UIImage *image = [UIImage imageWithData:contact.imageData];
if (image != nil) {
profileImage = image;
}else{
profileImage = [UIImage imageNamed:#"person-icon.png"];
}
for (CNLabeledValue *label in contact.phoneNumbers)
{
phone = [label.value stringValue];
if ([phone length] > 0) {
[contactNumbersArray addObject:phone];
}
}
NSDictionary* personDict = [[NSDictionary alloc] initWithObjectsAndKeys: fullName,#"fullName",profileImage,#"userImage",phone,#"PhoneNumbers", nil];
[arrayContacts addObject:[NSString stringWithFormat:#"%#",[personDict objectForKey:#"fullName"]]];
NSLog(#"The contacts are - %#",arrayContacts);
}
dispatch_async(dispatch_get_main_queue(), ^{
[tableViewShowContacts reloadData];
});
}
}
}];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - UITableView Data Source Methods
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return arrayContacts.count;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *strCell = #"cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:strCell];
if(cell==nil)
{
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:strCell];
}
cell.textLabel.text = arrayContacts[indexPath.row];
return cell;
}
The printed results of Contacts are
The contacts are - (
"John Appleseed",
"Kate Bell",
"Anna Haro",
"Daniel Higgins",
"David Taylor",
"Hank Zakroff"
)
I have a UITableViewController which should display all serviceName and Car Model. I have used subtitle as my UITableViewCell style. serviceName and Car Model are present in two different tables in Parse. I wrote the query and I am able to fetch the objectIDs of serviceName and Car model from the table. however when I try and use the objectIds to fetch the data from the respective tables I get a null value as return.
This is my currentjobs.h file
#import <UIKit/UIKit.h>
#import <Parse/Parse.h>
#interface currentJobs : UITableViewController{
NSMutableArray *currentjobs;
}
#property (strong, nonatomic) IBOutlet UITableView *currentjobTable;
#property (strong, atomic) NSString *servicerequestid;
#property (strong, atomic) NSString *serviceid;
#property (strong, atomic) NSString *carid;
#end
and this is my currentjobs.m file
#import "currentJobs.h"
#import <Parse/Parse.h>
#interface currentJobs ()
#end
#implementation currentJobs
#synthesize currentjobTable;
#synthesize servicerequestid;
#synthesize serviceid;
#synthesize carid;
- (void)viewDidLoad {
[super viewDidLoad];
NSString *mechanicid = [PFUser currentUser].objectId;
PFQuery *query1 = [PFQuery queryWithClassName:#"ServiceStatus"];
[query1 whereKey:#"mechanic" equalTo:mechanicid];
[query1 findObjectsInBackgroundWithBlock:^(NSArray *mechanicobjects, NSError *error) {
if (!error) {
currentjobs = [[NSMutableArray alloc] initWithArray:mechanicobjects];
NSLog(#"%#", currentjobs);
}
[currentjobTable reloadData];
}];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return currentjobs.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"currentjob" forIndexPath:indexPath];
// Configure the cell...
PFObject *mechanic = [currentjobs objectAtIndex:indexPath.row];
servicerequestid = [mechanic objectForKey:#"servicerequest"];
NSLog(#"%#", servicerequestid);
//query to find carid and serviceid
PFQuery *query2 = [PFQuery queryWithClassName:#"ServiceRequests"];
[query2 getObjectInBackgroundWithId:servicerequestid block:^(PFObject *servicerequestobject, NSError *error) {
if (!error){
serviceid = [servicerequestobject objectForKey:#"serviceName"];
carid = [servicerequestobject objectForKey:#"car"];
NSLog(#"%#", serviceid);
}
}];
//query to find servicename and display
PFQuery *query3 = [PFQuery queryWithClassName:#"services"];
[query3 getObjectInBackgroundWithId:serviceid block:^(PFObject *serviceNameobject, NSError *error) {
if (!error){
NSLog(#"objects Found");
cell.textLabel.text = [serviceNameobject objectForKey:#"serviceName"];
}
else if (error){
NSLog(#"Error Found");
NSLog(#"%#", error);
}
}];
//query to find car model and display
PFQuery *query4 = [PFQuery queryWithClassName:#"customerCars"];
[query4 getObjectInBackgroundWithId:carid block:^(PFObject *customercarobject, NSError *error) {
if (!error){
cell.detailTextLabel.text = [customercarobject objectForKey:#"model"];
}
}];
return cell;
}
#end
The query3 and query4 does seem to give me any kind of output. Where am I going wrong?
I don't think the problem has to do with setting the delegate and datasource to self, as when you create a UITableViewController these values are set by default. But all you need to do to test that is throw break points in those methods when the table view loads. This could be an asynchronous issue, meaning you don't actually have the data at the time you're trying to use it. I recommended cleaning up your cellForRowAtIndexPath method. I like to keep that method clean as what I'd do is actually use a DAO to hold my parse methods, or just have all those parse related calls in a method that can be called in the cellForRowAtIndexPath... Just looks better. Aside from that, I'm pretty sure this is an asynchronous issue, which is common when working with the numerous built in parse methods.
I don't see you are implementing UITableViewDataSource or UITableViewDelegate protocols. I don't see either that you set your CurrentJobs class as the delegate and datasource of your tableview. Without doing that, non of your UITableViewDelegate and UITableViewDatasource methods will be called in your CurrentJobs class.
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#interface Image : NSObject
#property (nonatomic, strong) NSString *name;
#property (nonatomic, strong) UIImage *image;
#property (nonatomic, strong) NSString *imageId;
#end
#import "Image.h"
#import <Parse/Parse.h>
#interface TableViewController()
#property (nonatomic, strong) NSMutableArray *array;
#property (nonatomic, strong) NSString *imageId;
#end
#implementation TableViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self fetchAllImages];
}
#pragma mark Retrieve All Images from Parse Server
-(void)fetchAllImages {
PFQuery *query = [PFQuery queryWithClassName:#"imagePost"];
[query findObjectsInBackgroundWithBlock:^(NSArray * _Nullable objects, NSError * _Nullable error) {
for(PFObject *tempParseObject in objects){
NSString *imageName = tempParseObject[#"name"];
PFFile *imageFile = tempParseObject[#"image"];
NSString *imageId = tempParseObject.objectId ;
UIImage *image = [UIImage imageWithData:imageFile.getData];
Image *retrievedImage = [[Image alloc] init];
retrievedImage.image = image;
retrievedImage.name = imageName;
retrievedImage.imageId = imageId;
[self.array addObject:retrievedImage];
}
}];
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.array.count;
}
-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"UITableViewCell"];
if(!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"UITableViewCell"];
}
Image *retrievedImage = [self.array objectAtIndex:indexPath.row];
cell.imageView.image = retrievedImage.image;
cell.textLabel.text = retrievedImage.name;
self.imageID = retrievedImage.imageId;
return cell;
}
#end
So as a quick side note.. this is not a good way to retrieve an Image/PFFile from parse as it will cause a long running operation in the main thread but its purpose here will serve as a simple example for how you should structure your code here. I query classname and retrieve the image, name and objectId, which happens when the view loads. I store them in temporary variables. Then i initialize my custom Image class, and assign its properties to the temporary variables. I then add that Image object to my array. This array is then used for the table view delegate and datasource methods. finally i create another Image variable and assign it to my array at indexpath.row and set the TEXT and IMAGE values of the UITableViewCell there.
The imageId is used if needed. I don't support naming conventions like naming an NSMutableArray as 'array', however, it should be a property over a plain ivar, though it technically won't make a difference in this case. Hope this simple example helps
I have a UITableViewController that I use to display the names of a list of local Business objects. I have used this UITableViewController
previously to read information from a .csv file and this was all working and displaying the Business names in my UITableViewController perfectly.
However, I have recently moved all this information to Parse.com and I am now trying to read and display the information from Parse.com in my current UITableViewController.
I am trying to refactor the code rather than use Parse.com's tutorials here:
https://www.parse.com/docs/ios_guide#ui-tables/iOS
In my viewDidLoad method, I call my own method [self initializeContent] which does the PFQuery to retrieve a list of objects that fit a certain criteria and then save the objects in an NSMutableArray.
This executes fine and I can see the Business objects are being retrieved through my NSLogs in my [self initializeContent] method and saved to a NSMutableArray (dataArray).
However, when stepping through the code, my dataArray is 0 in the numberOfSectionsInTableView method. Therefore I dont see the Business names displayed in the UITableViewController.
Does the numberOfSectionsInTableView get executed before the viewDidLoad method perhaps, meaning that my dataArray will always be 0?
I have stepped through the code but cant figure out where my issue is.
Below is my code - I have included as much as I can to help identify where my problem is.
BusinessDetailTableViewController.h
#import <UIKit/UIKit.h>
#import <Parse/Parse.h>
#interface BusinessDetailTableViewController : UITableViewController
#property (nonatomic, strong) NSMutableArray *dataArray;
#property (nonatomic, strong) NSMutableArray *defaultBusinessArrayMutable;
#property (nonatomic, assign) int tagNumber;
#property (nonatomic, strong) NSString *className;
#end
BusinessDetailTableViewController.m
#import "BusinessDetailTableViewController.h"
#import "BusinessDetailContent.h"
#import "DetailViewController.h"
#import <Parse/Parse.h>
#interface BusinessDetailTableViewController ()
#end
#implementation BusinessDetailTableViewController
#synthesize dataArray;
#synthesize defaultBusinessArrayMutable;
#synthesize tagNumber;
#synthesize className;
- (void)viewDidLoad
{
[super viewDidLoad];
[self initializeArrays];
[self initializeContent];
}
- (void) initializeArrays
{
dataArray = [[NSMutableArray alloc] init];
defaultBusinessArrayMutable = [[NSMutableArray alloc] init];
}
- (void) initializeContent
{
NSLog(#"Class Name: %#", className); // Shows the correct className
[self queryParse:className];
}
- (void) queryParse:(NSString *) className
{
PFQuery *query = [PFQuery queryWithClassName:self.className];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error)
{
if (!error)
{
NSLog(#"Successfully Retrieved %d Objects.", (int)objects.count); // Shows how many objects are retrieved
for (PFObject *object in objects)
{
BusinessDetailContent *businessDetailContent = [[BusinessDetailContent alloc] init];
businessDetailContent.no = object[#"ID"];
businessDetailContent.companyName = object[#"Company_Name"];
businessDetailContent.address = object[#"Address"];
businessDetailContent.tel = object[#"Tel"];
businessDetailContent.email = object[#"Email"];
businessDetailContent.website = object[#"Website"];
businessDetailContent.details = object[#"Details"];
businessDetailContent.imageName = object[#"Image_ID"];
// The information here is retrieved correctly
NSLog(#"BUSINESS DETAIL CONTENT - ID: %#, Company Name: %#, Address: %#, Tel: %#, Email: %#, Website: %#, Details: %#, Image_Name: %#",
businessDetailContent.no, businessDetailContent.companyName, businessDetailContent.address, businessDetailContent.tel,
businessDetailContent.email, businessDetailContent.website, businessDetailContent.details,
businessDetailContent.imageName);
[defaultBusinessArrayMutable addObject:businessDetailContent];
NSString *strFromInt = [NSString stringWithFormat:#"%d", tagNumber];
if (!([strFromInt isEqualToString:#"3"] || [strFromInt isEqualToString:#"10"]))
{
[dataArray addObject:defaultBusinessArrayMutable];
NSLog(#"Default Business Array Mutable Count: %i", (int)dataArray.count);
}
}
}
}
else
{
NSLog(#"Error retrieving objects: %# %#", error, [error userInfo]);
}
}];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#" cellForRowAtIndexPath "); // This is not executed
static NSString *CellIdentifier = #"Cell";
NSLog(#"Cell Identifier: %#", CellIdentifier);
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
// Configure the cell
NSArray *cellArray = [dataArray objectAtIndex:indexPath.section];
BusinessDetailContent *businessDetailContent = [cellArray objectAtIndex:indexPath.row];
cell.textLabel.text = businessDetailContent.companyName;
NSLog(#"Cell Textlabel Text: %#", cell.textLabel.text);
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
return cell;
}
- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#" didSelectRowAtIndexPath ");
[self performSegueWithIdentifier:#"businessDetailSegue" sender:self];
}
- (NSInteger) numberOfSectionsInTableView:(UITableView *)tableView
{
NSLog(#"numberOfRowsInSection returning: %d", (int)[dataArray count]);
return [dataArray count]; // dataArray = 0
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSMutableArray *array = [dataArray objectAtIndex:section];
return [array count];
}
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
NSLog(#"PrepareForSegue: %#", segue.identifier);
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
if ([segue.identifier isEqualToString:#"businessDetailSegue"] && indexPath)
{
NSArray *selectedRowArray = [dataArray objectAtIndex:indexPath.section];
BusinessDetailContent *selectedContent = [selectedRowArray objectAtIndex:indexPath.row];
DetailViewController *detailController = segue.destinationViewController;
detailController.businessDetailContent = selectedContent;
}
}a
#end
EDIT: I have added [self.tableView reloadData]; to the end of my findObjectsInBackgroundWithBlock method as many have suggested and now I am getting to the numberOfSectionsInTableView method, but the dataArray is still 0. And it looks as though my code is executing twice as can be seen by the Console print?
For my dataArray in my queryParse method it returns 18 objects - but there should be only 9.
From the Console print Hotel Array Count starts at 2 and increments by 2 each time after that.
And my numberOfRowsInSection is now getting executed twice, but before the Parse.com query.
I am totally stuck with this.
#Hans Moolman, yes you are correct, You just need to call the reload method of table view after your parsing is done. i .e.
- (NSMutableArray *) queryParse:(NSString *) className
{
PFQuery *query = [PFQuery queryWithClassName:self.className];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error)
{
if (!error)
{
NSLog(#"Successfully Retrieved %d Objects.", (int)objects.count); // Shows how many objects are retrieved
for (PFObject *object in objects)
{
BusinessDetailContent *businessDetailContent = [[BusinessDetailContent alloc] init];
businessDetailContent.no = object[#"ID"];
businessDetailContent.companyName = object[#"Company_Name"];
businessDetailContent.address = object[#"Address"];
businessDetailContent.tel = object[#"Tel"];
businessDetailContent.email = object[#"Email"];
businessDetailContent.website = object[#"Website"];
businessDetailContent.details = object[#"Details"];
businessDetailContent.imageName = object[#"Image_ID"];
// The information here is retrieved correctly
NSLog(#"BUSINESS DETAIL CONTENT - ID: %#, Company Name: %#, Address: %#, Tel: %#, Email: %#, Website: %#, Details: %#, Image_Name: %#",
businessDetailContent.no, businessDetailContent.companyName, businessDetailContent.address, businessDetailContent.tel,
businessDetailContent.email, businessDetailContent.website, businessDetailContent.details,
businessDetailContent.imageName);
[defaultBusinessArrayMutable addObject:businessDetailContent];
NSString *strFromInt = [NSString stringWithFormat:#"%d", tagNumber];
if (!([strFromInt isEqualToString:#"3"] || [strFromInt isEqualToString:#"10"]))
{
[dataArray addObject:defaultBusinessArrayMutable];
NSLog(#"Default Business Array Mutable Count: %i", (int)dataArray.count);
}
}
}
}
else
{
NSLog(#"Error retrieving objects: %# %#", error, [error userInfo]);
}
}];
// This line to be added
[self.tableView reloadData];
}
If still problems exists, do let me know. Thanks.
The call to parse.com returns data asynchronously, so the view will have finished loading and done its check for row count before the data is received. When the data is received you need to tell the table view to check the row count again and refresh - this is what you're missing.
[self.tableView reloadData];
and it should go in queryParse:, at the end of the completion block of findObjectsInBackgroundWithBlock:.
Hi first you set bool value default to false i mean viewdidload.
By parsing at didfinishloading function set bool to true
In numberofrowsinsection
if(!bool)
{
///just hide the tableview
}
else
{
[data count];
}
Scenario = I have an app that allows users to search for other users that use the service. In the search page there is a UISearchDisplayController that when a user begins typing in the search bar, a tableView will programmatically appear (just like any other UISearchDisplayController) and filter all of the users in the database depending on whats being typed ('begins-with'). So the user will begin typing, "B... r...." and users will begin to populate the tableView from "Brad" to "Brandon" and so on based on the text being inputted.
Question = How would one go about designing the parse query to achieve this effect?
Specific Questions =
1) When and Where to begin the initial query?...
PFQuery *searchQuery = [PFUser query];
[searchQuery whereKey:#"username" containsString:controller.searchBar.text];
[searchQuery orderByDescending:#"updatedAt"];
[searchQuery findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
NSLog(#"%#", objects);
searchArray = objects;
}];
in "searchDisplayControllerDidBeginSearch"?
2) When and Where do I put the logic to fill in the tableView?
PFObject *searchObject = [searchArray objectAtIndexPath:indexPath.row];
cell.nameLabel.text = searchObject[#"name"];
in "cellForRowAtIndexPath"?
If there is anyone out there that knows this and can help me out Id appreciate it.
Here is a simple example:
#import <Parse/Parse.h>
#interface MySearchController : PFQueryTableViewController
#end
And implementation
#import "MySearchController.h"
#interface MySearchController() <UISearchBarDelegate, UISearchDisplayDelegate>
#property (strong, nonatomic) IBOutlet UISearchBar *searchBar;
#property (nonatomic, strong) NSMutableArray *searchResults;
#end
#implementation MySearchController
- (id)initWithCoder:(NSCoder *)aCoder
{
self = [super initWithCoder:aCoder];
if (self) {
// get users
self.parseClassName = [PFUser parseClassName];
self.pullToRefreshEnabled = YES;
self.paginationEnabled = YES;
self.objectsPerPage = 10;
self.searchResults = [NSMutableArray new];
}
return self;
}
- (void)filterResults:(NSString *)searchTerm {
[self.searchResults removeAllObjects];
for (PFUser *user in self.objects)
{
NSString *username = user.username;
if ([[username lowercaseString] hasPrefix:[searchTerm lowercaseString]])
{
[self.searchResults addObject:user];
}
}
}
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString: (NSString *)searchString {
[self filterResults:searchString];
return YES;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return (tableView == self.tableView) ? self.objects.count : self.searchResults.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
PFUser *user = (tableView == self.tableView) ? self.objects[indexPath.row] : self.searchResults[indexPath.row];
static NSString *identifier = #"reuseIdentifier";
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:identifier];
if (!cell)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:identifier];
}
cell.textLabel.text = user.username;
return cell;
}
#end
The main thing to note about this that threw me off is that you have two table views, so you have to be careful.
One of the table views is from the original query, it will give you all users, that one is self.tableView. The other is from the search results, self.searchDisplayController.searchResultsTableView. The latter is active while searching. Therefore, you must return different values for each regular tableviewcontroller method. The number of rows is either self.objects.count or self.searchResults.count. The correct user is either self.objects[indexPath.row] or self.searchResults[indexPath.row]. It is easy to check which table view you're dealing with in a given protocol method, just use this condition:
(tableView == self.tableView)
I have to create a UITableView using the JSON response below ( Array ). I have no code for this yet but would love some direction to how i would split this array to accommodate categories and items on all levels.
{
"result":{
"products":[
{
"id":"4",
"product_code":"PR04",
"title":"Product1",
"franchisee_id":"118"
}
],
"categories":[
{
"id":"8",
"name":"Category1"
},
{
"id":"20",
"name":"Category2",
"products":[
{
"id":"9",
"product_code":"PR07",
"title":Product2,
"franchisee_id":"118"
}
]
}
]
}
}
I want to achieve the following result:
items
Category1 > items
Category2 > items
When a category is clicked it would slide to the products in that category. Would really love some direction on this. Some products will not be in categories. Like the example above.
Well....
You need to parse the JSON file. You can easily google for some tutorials but here is a decent one.
Next you are going to need to setup a UITableView to load the items. another good tutorial on UITableViews
Then you are going to need to learn how to pass data between UIViewControllers. Tutorial.
So your steps in the code will be to:
Parse the JSON to separate all the elements.
Setup a UITableView to display the top level elements.
Create a second UITableViewController to push to after a top level item has been selected.
Setup a custom initializer for the second UITableViewController so you can pass it relevant data from the first view controller where you parsed the JSON.
I'm assuming you were looking for a bunch of code on how to do this, but that's no fun :)
Let me know if you run into any troubles and I will be glad to help.
EDIT:
I know I said I wasn't going to dump code but I have some extra time.
Create an NSObject subclass called ProductObject and make the .h look like this:
#import <Foundation/Foundation.h>
#interface ProductObject : NSObject
#property NSString *productCode, *productTitle, *franchiseId, *productId;
#end
Don't do any thing to the .m
Create another NSObject subclass called CategoryObject and make the .h look like this:
#import <Foundation/Foundation.h>
#interface CategoryObject : NSObject
#property NSString *categoryName, *categoryId;
#property NSArray *products;
#end
Again, don't need to do anything to the .m.
Now, in the class that you want to display the UITableView will the Products and Categories (this is all in the .m, the .h is empty):
#import "ViewController.h"
#import "CategoryObject.h"
#import "ProductObject.h"
#interface ViewController ()
//Hooked in from IB
#property (weak, nonatomic) IBOutlet UITableView *table;
//Our UITableView data source
#property NSMutableDictionary *tableObjects;
#end
#implementation ViewController
/**
Parses a the local JSON file
*/
- (void)parseJSON {
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"test" ofType:#"json"];
//création d'un string avec le contenu du JSON
NSString *myJSON = [[NSString alloc] initWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:NULL];
NSError *error;
NSDictionary *topLevleJSON = [NSJSONSerialization JSONObjectWithData:[myJSON dataUsingEncoding:NSUTF8StringEncoding] options:kNilOptions error:&error];
if (error) {
NSLog(#"Error serializing JSON: %#", error.localizedDescription);
return;
}
NSArray *products = topLevleJSON[#"products"];
NSArray *categories = topLevleJSON[#"categories"];
//Use a NSDictonary so that it contains an NSArray of ProductObjects for the "Products" key, and an array of CategoryObjects for the "Category" key.
self.tableObjects = [NSMutableDictionary new];
//Parse all the products
NSMutableArray *productsInJSON = [NSMutableArray new];
[products enumerateObjectsUsingBlock:^(NSDictionary *productObject, NSUInteger idx, BOOL *stop) {
ProductObject *product = [self createProductObjectFromDictionary:productObject];
[productsInJSON addObject:product];
}];
//Set the array of ProductObjects for the key #"Products"
[self.tableObjects setObject:productsInJSON forKey:#"Products"];
//Parse all the categories
NSMutableArray *categoriesInJSON = [NSMutableArray new];
[categories enumerateObjectsUsingBlock:^(NSDictionary *categoryObject, NSUInteger idx, BOOL *stop) {
CategoryObject *category = [self createCategoryObjectFromDictionary:categoryObject];
[categoriesInJSON addObject:category];
}];
//Set the array of CategoryObjects for key #"Categories"
[self.tableObjects setObject:categoriesInJSON forKey:#"Categories"];
[self.table reloadData];
}
/**
Creates a ProductObject from an NSDictonary.
#param dictionary The dictonary describing the Product parsed from JSON
#return A pretty formatted ProductObject
*/
- (ProductObject*)createProductObjectFromDictionary:(NSDictionary*)dictionary {
ProductObject *product = [ProductObject new];
product.productTitle = dictionary[#"title"];
product.productCode = dictionary[#"product_code"];
product.franchiseId = dictionary[#"franchisee_id"];
product.productId = dictionary[#"id"];
return product;
}
/**
Creates a Category from an NSDictionary
#param dictionary The dictonary describing the Category parsed from JSON
#return A pretty formatted CategoryObject
*/
- (CategoryObject*)createCategoryObjectFromDictionary:(NSDictionary*)dictionary {
CategoryObject *category = [CategoryObject new];
category.categoryId = dictionary[#"id"];
category.categoryName = dictionary[#"name"];
//Check to see if the "products" key exist for the category, if we don't check and just look for it, we will get a crash if it doesn't exist.
if ([[dictionary allKeys] containsObject:#"products"]) {
NSArray *categoryProducts = dictionary[#"products"];
//Parse all the Products for the Category.
NSMutableArray *categoryProductsFormatted = [NSMutableArray new];
[categoryProducts enumerateObjectsUsingBlock:^(NSDictionary *productObject, NSUInteger idx, BOOL *stop) {
ProductObject *product = [self createProductObjectFromDictionary:productObject];
[categoryProductsFormatted addObject:product];
}];
category.products = [NSArray arrayWithArray:categoryProductsFormatted];
}
else {
category.products = nil;
}
return category;
}
#pragma mark -
#pragma mark - UITableView delegate
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return [[self.tableObjects allKeys] count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
//Get the key for this section
NSString *key = [[self.tableObjects allKeys] objectAtIndex:section];
//Return the number of objects for this key.
return [(NSArray*)[self.tableObjects objectForKey:key] count];
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
return [[self.tableObjects allKeys] objectAtIndex:section];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"CellIdentifier"];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"CellIdentifier"];
}
//Get all the NSArray associated with this section, which will be an array of ProductObjects or an array of CategoryObjects
NSString *key = [[self.tableObjects allKeys] objectAtIndex:indexPath.section];
NSArray *sectionobjects = (NSArray*)[self.tableObjects objectForKey:key];
id object = [sectionobjects objectAtIndex:indexPath.row];
//Set the cell text based on what kind of object is returned
if ([object isKindOfClass:[ProductObject class]]) {
cell.textLabel.text = [(ProductObject*)object productTitle];
}
else if ([object isKindOfClass:[CategoryObject class]]) {
cell.textLabel.text = [(CategoryObject*)object categoryName];
}
return cell;
}
#pragma mark -
#pragma mark - UITableView delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
NSString *key = [[self.tableObjects allKeys] objectAtIndex:indexPath.section];
NSArray *sectionobjects = (NSArray*)[self.tableObjects objectForKey:key];
id object = [sectionobjects objectAtIndex:indexPath.row];
//They selected a product
if ([object isKindOfClass:[ProductObject class]]) {
ProductObject *product = (ProductObject*)object;
NSLog(#"%#", product.productTitle);
NSLog(#"%#", product.productCode);
NSLog(#"%#", product.productId);
}
//They selected a Category
else if ([object isKindOfClass:[CategoryObject class]]) {
//Check to see if the CategoryObject has any ProductObjects associated with it
if ([(CategoryObject*)object products]) {
//Now you will need to pass array of ProductObjects this along to your next view controller.
NSArray *cateogryProducts = [(CategoryObject*)object products];
//For demonstration purposes, i'll run through and print out all the Products for this Category
[cateogryProducts enumerateObjectsUsingBlock:^(ProductObject *product, NSUInteger idx, BOOL *stop) {
NSLog(#"%#", product.productTitle);
NSLog(#"%#", product.productCode);
NSLog(#"%#", product.productId);
}];
}
}
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
//Start parsing the JSON
[self parseJSON];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
EDIT:
If you are wanting to open and close parts of the table like an accordion, take a look at Apple's same code: Table View Animations and Gestures.