How to Use Relational Queries Parse iOS to populate a UITableView? - ios

I'm developing a social app and have some problems with populating objects from parse created in a selected cell.
The Home screen is a UITableViewController, populates an array of objects stored in Parse, when a user taps a cell, a new scene DetailViewController will be pushed, which shows the object from the selected cell in a view.
Next, I created in DetailViewController a UIButton to add objects to a new class called "replies" and also have added into the DetailViewController scene, a TableView which is populated using a new array from those objects at the "replies" class.
I want to retrieve just the objects created on the selected cell :( At the moment everything works fine except on the DetailViewController scene the TableView will show all the objects created in the "replies" class no matter on which cell I tap.
I wonder how would I store an array from the selected cell so I can populate the objects that have been created at that selected cell... I believe that I could solve this issue with relational queries from parse at the retrieve from parse method.
Would Appreciate Very Much Any Help.
.h file
#import <UIKit/UIKit.h>
#import <Parse/Parse.h>
#import <ParseUI/ParseUI.h>
#import "GroundTableViewCell.h"
#import "TimelineTableViewController.h"
#interface DetailViewController : UIViewController<
UITableViewDataSource,
UITableViewDelegate,
NSObject>
{
}
#property (strong, nonatomic) IBOutlet UITableView *detailTableView;
#property (strong, nonatomic) IBOutlet UITextView *TextField;
#property (nonatomic, strong) PFObject *groUnds;
#property (strong, nonatomic) IBOutlet UITextView *replyTextView;
- (IBAction)sendReply:(id)sender;
#end
.m file
#import "DetailViewController.h"
#import <Parse/Parse.h>
#import "GroundTableViewCell.h"
#import "TimelineTableViewController.h"
#interface DetailViewController ()
#property(strong)NSMutableArray* repliesMutableArray;
#end
#implementation DetailViewController
#synthesize groUnds;
#synthesize TextField;
#synthesize detailTableView;
#synthesize repliesMutableArray;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
PFUser *currentUser = [PFUser currentUser];
if (currentUser) {
// do stuff with the user
} else {
// show the signup or login screen
}
// Do any additional setup after loading the view.
// [self performSelector:#selector(retrieveFromParse)];
// repliesArray = [[NSArray alloc] initWithObjects:#"comment", nil];
[self performSelector:#selector(retrieveFromParse)];
// Set the Label text with the selected detail.
self.TextField.text = [self.groUnds objectForKey:#"comment"];
}
- (void) retrieveFromParse {
PFQuery *retrieveReplies = [PFQuery queryWithClassName:#"Replies"];
// This is the part where im not really sure how to query...
// if uncommented won't show any objects at all now shows all the objects from the "Replies class" -->
// [retrieveReplies whereKey:#"comment" equalTo:groUnds];
[retrieveReplies orderByDescending:#"createdAt"];
[retrieveReplies findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
repliesMutableArray = [[NSMutableArray alloc] initWithArray:objects];
}
[detailTableView reloadData];
}];
}
//*********************Setup table of folder names ************************
//get number of sections in tableview
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// Return the number of sections.
return 1;
}
//get number of rows by counting number of folders
-(NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return [repliesMutableArray count ];
}
//setup cells in tableView
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"replyCell";
GroundTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
PFObject *tempObject = [repliesMutableArray objectAtIndex:indexPath.row];
cell.cellTitle.text = [tempObject objectForKey:#"commentReplies"];
return cell;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
//reply button
- (IBAction)sendReply:(id)sender {
//1
//Add the image to the object, and add the comment and the user
PFObject *Reply = [PFObject objectWithClassName:#"Replies"];
[Reply setObject:[PFUser currentUser].username forKey:#"user"];
[Reply setObject:self.replyTextView.text forKey:#"commentReplies"];
//2
[Reply saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
//3
if (succeeded){
//Go back to the wall
[self.navigationController popViewControllerAnimated:YES];
}
else{
NSString *errorString = [[error userInfo] objectForKey:#"error"];
UIAlertView *errorAlertView = [[UIAlertView alloc] initWithTitle:#"Error" message:errorString delegate:nil cancelButtonTitle:#"Ok" otherButtonTitles:nil, nil];
[errorAlertView show];
}
}];
}
#end

Overview
The basic structure of passing information between view controllers is that you need to set the data you want to display in the Detail View Controller (DVC) while you are in the main view controller (MVC). So on the delegate method in the MVC which is called when you select a cell (didSelectRowAtIndexPath) you can get the data specific to the selected cell and place it into a variable. Inside of the prepareForSegue... method you can set that data to be a variable on the dvc that represents your tableview data.
Passing Data Between View Controllers
To see how to properly pass data between view controllers look at the answers to this highly rated question on SO.
You can think of this as a push instead of pull. Data should be added to a property on the DVC before the view is presented. There are a number of ways to limit the data using NSPredicates, SubQueries, Related Queries or Queries on Array Values:
Queries on Array Values
For keys with an array type, you can find objects where the key's array value contains 2 by:
// Find objects where the array in arrayKey contains 2.
// Using PFQuery
[query whereKey:#"arrayKey" equalTo:#2];
// Or using NSPredicate
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"2 IN arrayKey"];
PFQuery *query = [PFQuery queryWithClassName:#"MyClass" predicate:predicate];
(more Parse Queries)
Keep in mind that you can also handle this problem of refining the data by using Apple API solutions to refine the data you add onto the DVC property. You mentioned that you are new so please make sure you have read and completed the tutorial on the getting started link below. It's important because it shows Apple's paradigm for handling data movement in iOS.
Getting Started
Please review the getting started guide if the explanation doesn't make sense. There are critical concepts that cannot be glossed over or easily explained in the context of this answer.
Proposed Resolution
Pass in the indexPath.row of the selected cell to detail view controller so that you can limit the results of the array to that cell. If the indexPath row is not useful for this purpose then just pass a value that will limit the results to something unique within the selected cell. Use the delegate method didSelectRowAtIndexPath on your tableview delegate to set a relevant data value that you pass to the object managing the underlying tableview on the detail scene. You will want to identify a "where-clause" that is appropriate to the data you want to show in the DVC and then use either a new query or an nspredicate to limit the results from your array of data. Search SO for examples of each of these concepts.
Hopefully these tips will help you to find the ultimate answer. Searching SO will give you examples that you then need to apply to your specific situation. However, I really recommend that you step back for a moment to make sure you understand how to pass data between view controllers correctly.

Related

Objective-c UITableView separate class cellForRowAtIndexPath does not get called

There are a huge number of questions relating to this topic but I have not yet come across my use case so here goes.
This is my first couple weeks in OBJ-C so I have no clue what I am doing with some of this stuff...
What I Want
I do not particularly enjoy seeing so many classes in OBJ-C that overload the view controller classes with every and any function on this earth. It looks dirty and feels gross as far as OOP goes. In my use case I don't have a full screen table just a little one to hold 10 things. Therefore it's quite inappropriate to use a full UITableViewController. Instead, I want to have all my table delegate specific methods to be in a UITableView sub-class. NOT in a UITableViewController or a ViewController with a UITableView property. This should be mega simple yet...
The Problem
No matter what I do I cannot seem to get the method cellForRowAtIndexPath to fire. I know enough to know that this stuff relies heavily on the delegate and datasource assignment... however since I have a separate UITableView class that uses the <UITableViewDelegate, UITableViewDataSource> delegations I don't think I should have to do any sort of assignment at all!
What am I gonna write?? self.delegate = self ? or worse, in the ViewController that calls this UITableView class, self.tasksTable.delgate = self.tasksTable ? Eww... gross
Here is what I am doing in code.
The Code
TasksTableView.h
#import <UIKit/UIKit.h>
#interface TasksTableView : UITableView <UITableViewDelegate, UITableViewDataSource> {
NSArray *tasksData;
}
- (NSMutableArray *)getAllTasks;
#end
TasksTableView.m
#import "TasksTableView.h"
#import "NSObject+RemoteFetch.h" //<--I use this to fetch, obvs
#interface TasksTableView ()
#property (nonatomic, strong) NSString *cellId;
#end
#implementation TasksTableView
- (instancetype)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
if(self) {
_cellId = #"AllTasksTableCell";
tasksData = [self getAllTasks];
}
return self;
}
#pragma mark - Custom Table Functionality
- (NSMutableArray *)getAllTasks {
#try {
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSString *TASKS_URL = [userDefaults objectForKey:#"tasksUrl"];
NSObject *fetcher = [[NSObject alloc] init];
NSDictionary *response = [fetcher fetchAPICall:TASKS_URL httpRequestType:#"GET" requestBodyData:nil];
return [response objectForKey:#"data"];
} #catch (NSException *exception) {
NSLog(#"could not get tasks, error: %#", exception);
return nil;
}
}
#pragma mark - UITableView DataSource Methods
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [tasksData count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
//<-- NEVER GETS HERE
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:_cellId];
if(cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:_cellId];
}
cell.textLabel.text = [tasksData objectAtIndex:indexPath.row];
return cell;
}
#end
I am also having a hard time figuring out what to set as the datasource. In other languages you would typically set the DataSource object with self.DataSource = [self getAllTasks]... however all the tuturials I have done thus far all tend to use some weird ad-hoc NSArray or NSDictionary to then correlate the index of the table functions with the index of the array or dictionary keys... This confuses me greatly as to why I can't just set the DataSource object and have the table know to iterate over it's data.
My conclusion is that this isn't firing because it thinks the DataSource object is empty and there are no rows? (which it is, but like I said people seem to get Tables to work fine on YouTube doing this)
Thanks.
TasksTableView class is derived from UITableView class & You are implementing the UITableview delegates in the same class. This will not work.
Instead of creating a UITableView subclass. Create TasksTableView class as NSObject sub class. And pass the tableview object from where you added a tableview.
#interface TasksTableView : NSObject <UITableViewDelegate, UITableViewDataSource> {
NSArray *tasksData;
__weak UITableView *tableView;
}
And set that table view delegate to self(TasksTableView object) while init the TasksTableView Class
- (instancetype)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
if(self) {
_cellId = #"AllTasksTableCell";
tasksData = [self getAllTasks];
self.tableView.delgate = self;
self.tableView.datasource = self;
}
return self;
}
Now your delegate methods will trigger for that specific tableview

Objective C - TableViewController embedded in a ViewController - What triggers the table view to process the data?

I'm building an iPad app that will have a table view in one sector of the window with other labels, images, buttons, etc elsewhere. I know how to create a tableView when using a tableViewController as the class of the scene, but can't figure out how to populate the table cells when the table view is embedded in a scene with a UIViewController class.
I have updated my .h file as follows:
#interface SKMainViewController : UIViewController <UITableViewDataSource, UITableViewDelegate>
I'm returning data from an asynchronous API call into the viewController, but can't figure out how to initiate the required tableView methods (tableView:numberOfRowsAtIndexPath and tableView:cellForRowAtIndexPath). How can I trigger them to run once I've got my data back? Is there a simple method call I don't know about?
I've dissected a functioning UITableViewController class and don't see what triggers the population of the table cells.
EDITED: Adding large sections of code for your review. I've trimmed out the other imports and property declarations to simplify.
This is SKMainViewController.h:
#import "SKWelcomeViewController.h"
#import "SKAgenda.h"
#import "SKAgendaManager.h"
#import "SKAgendaCommunicator.h"
#import "SKAgendaManagerDelegate.h"
#import "SKAgendaTableViewCell.h" // I'm using a custom cell
#interface SKMainViewController : UIViewController <SKFlipsideViewControllerDelegate, UIPopoverControllerDelegate, NSURLConnectionDelegate, SKGreetingManagerDelegate, SKWeatherManagerDelegate, UITableViewDataSource, UITableViewDelegate>
{
NSArray *_agenda;
SKAgendaManager *_aManager;
}
#pragma mark Agenda Detail
#property (strong, nonatomic) NSArray *agendaItems;
#property (strong, nonatomic) IBOutlet UITableView *agendaTableView;
#end
This is SKMainViewController.m:
#import "SKMainViewController.h"
#interface SKMainViewController ()
#end
#implementation SKMainViewController
- (void)viewDidLoad
{
[self startFetchingAgenda:_agendaItems];
}
-(void)startFetchingAgenda:(NSNotification *)notification
{
NSInteger deviceID = [[NSUserDefaults standardUserDefaults] integerForKey:#"deviceID"];
if(deviceID == 0)
{
// todo: add error handling here
NSLog(#"Bad stuff happened");
}
[_aManager fetchAgendaForDeviceID:deviceID];
}
-(void)didReceiveAgenda:(NSArray *)agendaItems
{
NSLog(#"Received these agenda items: %#", agendaItems);
_agendaItems = agendaItems;
if(agendaItems.count == 0)
{
// set defaults to populate a single cell
NSLog(#"No Items!");
}
else
{
// populate data and present
NSLog(#"Some Items!");
agendaTableView.delegate = self; // Error Here: use of undeclared identifier 'agendaTableView'
agendaTableView.dataSource = self; // Error Here: use of undeclared identifier 'agendaTableView'
}
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 2;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return [_agendaItems count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
[self.agendaSpinner startAnimating];
self.agendaSpinner.hidden = NO;
static NSString *tableIdentifier = #"agendaTableCell";
SKAgendaTableViewCell *cell = (SKAgendaTableViewCell *)[tableView dequeueReusableCellWithIdentifier:tableIdentifier];
if(cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"agendaTableCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
// set the data related to the agenda item
SKAgenda *agendaDetails = [_agendaItems objectAtIndex:indexPath.row];
cell.agendaTitle.text = agendaDetails.title;
cell.agendaDescription.text = agendaDetails.description;
NSString *eventTimes = [NSString stringWithFormat:#"%# - %#", agendaDetails.start, agendaDetails.end];
cell.agendaTimes.text = eventTimes;
//todo: add functionality for background and text colors. Will need to use RGB colors instead of HEX. Change in webiste.
// todo: accommodate for no events in the agenda
[self.agendaSpinner stopAnimating];
self.agendaSpinner.hidden = YES;
return cell;
}
Thanks.
Just set the data source and delegate:
myTableView.delegate = self;
myTableView.dataSource = self;
Where self is your SKMainViewController that adopts the UITableViewDataSource and UITableViewDelegate protocols.
If at any time you'd like to reload your table data:
[myTableView reloadData];
Yes, the simple method is reloadData. This causes the table view to call its data source methods. You need to put it in a completion block (or delegate that's called after the data is received) if you're using an asynchronous api.

Objective C - Passing data from second VC with tableview to first VC

Already manage to pass input text from first VC to second VC with tableview.
First VC do not have tableview.
First VC:
UItextField - user type some name.
UIButton *add - button with segue (prepareForSegue)
Second VC:
TableView displaying input text from first VC with prepareForSegue
Question:
Tableview displays only one row at the time, so when i click back to input another name, and click add buton, tableview obviously gets reset and does not remember first input text. So how get tableview to remember names and put it in other rows. I don't know should i type code in prepareForSegue, or make delegate in first VC. Please explain in detail. Thank you alot.
I believe there are many ways to achieve this, if you want to persist data maybe core Data is your best bet, however if its just a simple logic then I suggest you using delegates.
ViewController
Interface
#interface ViewController : UIViewController
#property (strong, nonatomic) IBOutlet UITextField *dataTextField;
#property (nonatomic) NSMutableArray *items;
- (IBAction)AddData:(id)sender;
#end
Implementation
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
_items = [[NSMutableArray alloc]init];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)AddData:(id)sender {
if ([self.dataTextField.text length]> 0) {
[_items addObject:self.dataTextField.text];
[self performSegueWithIdentifier:#"tableSegue" sender:self];
}else{
UIAlertView *alertView = [[UIAlertView alloc]initWithTitle:#"Error"
message:#"You must enter some data"
delegate:self
cancelButtonTitle:#"OK" otherButtonTitles:nil, nil];
[alertView show];
}
}
- (void)addItemViewController:(TableViewController *)controller didFinishSelectingItem:(NSMutableArray *)item selectedTag:(int)tag{
NSLog(#"DATA=%#", item);
}
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if([segue.identifier isEqualToString:#"tableSegue"]){
TableViewController *controller = (TableViewController *)segue.destinationViewController;
controller.items = _items;
}
}
#end
TableViewController
Interface:
#protocol TableViewControllerDelegate <NSObject>
- (void)addItemViewController:(id)controller didFinishSelectingItem:(NSMutableArray *)item selectedTag:(int)tag;
#end
#interface TableViewController : UITableViewController
#property (nonatomic, weak) id <TableViewControllerDelegate> delegate;
#property (nonatomic) NSMutableArray *items;
#end
Implementation
#implementation TableViewController
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [self.items count];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[self.navigationController popViewControllerAnimated:YES];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cell" forIndexPath:indexPath];
[[cell textLabel] setText:_items[indexPath.row]];
return cell;
}
#end
View controllers are part of the controller layer of your application. They do not do "business logic" — heavy processing or more-persistent storage of data, whether to disk or just for that session.
The model section of your application handles that. Each view controller gets and sets data via the model. There should be no ongoing conversations between view controllers*; anything beyond things you would specify to an init is indicative of a broken design.
So you're asking the wrong question.
You would have a model that somehow vends the items that should go into the first view controller. You will have a second view controller that knows how to edit one item. The level of communication from first to second will be "this is the item you should be editing".
It is the responsibility of the first view controller and the model to ensure that it can keep its display up to date. The second view controller is responsible only for modifying its record. It shouldn't need to communicate anything whatsoever to the first view controller.
Whether you do that by pulling results from the model on every viewWillAppear, by some sort of live observation, by notifications emanating outward from the model or by some other means entirely doesn't matter.
(* subject to caveats where you've used containment, e.g. changes to the title that a view controller has but which is shown by a navigation controller are technically an ongoing conversation)

"Attempt to insert row 0 into section 0, but there are only 0 rows in section 0 after the update" Error

I have an app that is selecting a person from their contacts list and takes their First name, last name and email. It then saves the first name to a nsmutablearray and puts it into a uitableview cell. My problem occurs once the contact is selected in the simulator.
Code:
.h:
#import <UIKit/UIKit.h>
#import <AddressBookUI/AddressBookUI.h>
#interface FirstViewController : UIViewController < ABPeoplePickerNavigationControllerDelegate, UITableViewDelegate, UITableViewDataSource>
- (IBAction)showPicker:(id)sender;
#property (weak, nonatomic) IBOutlet NSString *firstName;
#property (weak, nonatomic) IBOutlet NSString *email;
#property (weak, nonatomic) IBOutlet NSString *lastName;
#property (weak, nonatomic) IBOutlet UITableView *myTableView;
#property (strong, nonatomic) NSMutableArray *contacts;
#end
.m:
#import "FirstViewController.h"
#interface FirstViewController ()
#end
#implementation FirstViewController
#synthesize firstName;
#synthesize email;
#synthesize lastName;
#synthesize contacts;
#synthesize myTableView;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
contacts = [[NSMutableArray alloc]initWithObjects:nil];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - UITableView Datasource
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return contacts.count;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
cell.textLabel.text = [contacts objectAtIndex:indexPath.row];
return cell;
}
- (IBAction)showPicker:(id)sender {
ABPeoplePickerNavigationController *picker =
[[ABPeoplePickerNavigationController alloc] init];
picker.peoplePickerDelegate = self;
[self presentModalViewController:picker animated:YES];
}
- (void)peoplePickerNavigationControllerDidCancel:
(ABPeoplePickerNavigationController *)peoplePicker
{
[self dismissModalViewControllerAnimated:YES];
}
- (BOOL)peoplePickerNavigationController:
(ABPeoplePickerNavigationController *)peoplePicker
shouldContinueAfterSelectingPerson:(ABRecordRef)person {
[self displayPerson:person];
[self dismissModalViewControllerAnimated:YES];
return NO;
}
- (BOOL)peoplePickerNavigationController:
(ABPeoplePickerNavigationController *)peoplePicker
shouldContinueAfterSelectingPerson:(ABRecordRef)person
property:(ABPropertyID)property
identifier:(ABMultiValueIdentifier)identifier
{
return NO;
}
- (void)displayPerson:(ABRecordRef)person
{
NSString* name = (__bridge_transfer NSString*)ABRecordCopyValue(person,
kABPersonFirstNameProperty);
self.firstName = name;
NSString* last = (__bridge_transfer NSString*)ABRecordCopyValue(person,
kABPersonLastNameProperty);
self.lastName = last;
ABMultiValueRef emails = ABRecordCopyValue(person, kABPersonEmailProperty);
NSString *emailId = (__bridge NSString *)ABMultiValueCopyValueAtIndex(emails, 0);//0 for "Home Email" and 1 for "Work Email".
self.email = emailId;
if (!(contacts))
{
contacts = [[NSMutableArray alloc]init];
}
[contacts insertObject:firstName atIndex:0];
NSIndexPath * indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
[self.myTableView insertRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
}
#end
UITableView must be kept in sync with the data source at all times. Special care must be taken if the data source can change in a background thread.
When something is added to the data source, call beginUpdate/insert/endUpdate as soon as possible. You don't have to worry about caching these, the UITableView will cache changes to be executed when it determines there is enough cpu time and resources.
The moment endUpdates is called, the UITable will ask the dataSource for the number of sections and rows again. If your number of sections and row feeds directly from the dataSource, then number sections and rows, plus insertions, minus deletions must equal the numbers returned by the end calls for numberOfSections and numberOfRowsInSection.
One last tip: avoid mixing calls to 'reloadData' and beginUpdate/endUpdate pairs. Use one or the other, not both.
I have encountered same problem as this. All you have to do is change
[self.myTableView insertRowsAtIndexPaths:#[indexPath]withRowAnimation:UITableViewRowAnimationAutomatic];
to
[self.myTableView beginUpdates];
[self.myTableView insertRowsAtIndexPaths:#[indexPath]withRowAnimation:UITableViewRowAnimationAutomatic];
[self.myTableView endUpdates];
From UITableView Documentation
beginUpdates
Begin a series of method calls that insert, delete, or select rows and sections of the receiver.
When you use beginUpdates, you must call endUpdates and not reloadData.
You can check this link for more UITableView information.
The comments above about implementing -tableView:numberOfRowsInSection: are correct. The assertion is checking the returned value expecting it will increase.
However since you didn't set your dataSource on the UITableView its calling (or not calling) a method that doesn't exist and getting 0.
You need to set the myTableView.dataSource and (since you also implement the delegate protocol) myTableView.delegate to self.
You're also likely to need something like
- (void)viewDidLoad
{
[super viewDidLoad];
[self.myTableView registerClass:[UITableViewCell class] forCellReuseIdentifier:#"Cell"];
...
}
Unless you're registering that somewhere else or your storyboard has a "Prototype Cell" with the identifier "Cell" which your code asks for.
I find this problem commonly occurs when I am placing a table view inside of a View Controller. If you're using a UITableViewController jump to 3.
These steps may help:
1: In your View Controller .h file make sure you add the following:
#interface YourViewController : UIViewController <UITableViewDataSource, UITableViewDelegate>
2: Next create an IBOutlet for your table view by ctrl + drag to your .h class. It should look like:
#property (weak, nonatomic) IBOutlet UITableView *tableView;
3: Next step is to ctrl + drag to your View Controllers icon (see image)
You need to do this twice selecting:
- delegate
- datasource
Finally, in your .m file, you should have the following method:
- (void) viewWillAppear:(BOOL)animated{
//[self.tableView beginUpdates];
//[self.tableView endUpdates];
[self.tableView reloadData];
}
You can use either beginUpDates/endUpdates or reloadData, however Apple docs recommend reloadData.
Once done your table should work fine.
You need to maintain count for contacts array and increment accordingly.
and while creating indexPath you need to set appropriate indexPathForRow: and section count(if required).
- (void)displayPerson:(ABRecordRef)person
{
..........
[contacts insertObject:firstName atIndex:0];
NSIndexPath * indexPath = [NSIndexPath indexPathForRow:0 inSection:0]; // you cant do this increment appropriately
.........
}
Please check your tableview datasource and delegate methods. You may be passing empty array to datasource methods. And don't forget to reload tableview after getting data.
I know it's stupid - I got the same error because I forgot to set the delegate and dataSource.
So after inserting rows and doing tableView.endUpdates() the tableView thought it must have some rows - but due to the unlinked dataSource, it has not.

How to set up UITableView within a UIViewController created on a .xib file

I have a class like this:
#interface ExerciseLogDetails : UIViewController<UIActionSheetDelegate, UITableViewDelegate, UITableViewDataSource> {
where I am trying to display some elements followed by a UITextView. The UITextView element is created on Interface Builder. When executing this code:
- (void)viewDidLoad {
self.tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];
tableView.dataSource = self;
tableView.delegate = self;
[self.view addSubview:self.tableView];
}
a table shows, but not the one I configured in Interface Builder. It is completely blank and unformatted. How can I access my table and populate it progrmmatically with data?
Thank you!
Several of the tips on this thread helped me create this. I am going to offer some more complete code files in order to help others as well:
Step 1. Drag your UITableView onto your View Controller either in Storyboards or XIBs. In my example I am using a story board.
Step 2: Open your ViewController (in my case its just DefaultViewController) and add the two delegates for the UITableView: UITableViewDelegate and UITableViewDataSource. Also add a simple data source for population and the UITableView IBOutlet.
DefaultViewController.h
#import <UIKit/UIKit.h>
#interface DetailViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
#property (strong, nonatomic) IBOutlet UITableView *tableView;
#property (strong, nonatomic) NSMutableArray *newsArray;
#end
Step 3: Open your implementation file (DefaultViewController.m) and add the following:
#import "DetailViewController.h"
#interface DetailViewController ()
- (void)configureView;
#end
#implementation DetailViewController
#synthesize newsArray;
#synthesize tableView;
#pragma mark - Managing the detail item
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self configureView];
}
- (void)configureView
{
// Update the user interface for the detail item.
self.newsArray = [[NSMutableArray alloc] initWithObjects:#"Hello World",#"Goodbye World", nil];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark UITableViewDelegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// typically you need know which item the user has selected.
// this method allows you to keep track of the selection
}
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView
editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath
{
return UITableViewCellEditingStyleDelete;
}
// This will tell your UITableView how many rows you wish to have in each section.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [self.newsArray count];
}
// This will tell your UITableView what data to put in which cells in your table.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifer = #"CellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifer];
// Using a cell identifier will allow your app to reuse cells as they come and go from the screen.
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifer];
}
// Deciding which data to put into this particular cell.
// If it the first row, the data input will be "Data1" from the array.
NSUInteger row = [indexPath row];
cell.textLabel.text = [self.newsArray objectAtIndex:row];
return cell;
}
#end
Step 4: Goto your Storyboards or XIB and select your UITableView and drag the datasource and delegate outlets onto your DefaultViewController to wire them up. Also you will need to wire up the Referencing Outlet for the UITableView to your IBOutlet tableView object you created in your header file.
Once this is finished you should be able to run it and the sample data will be in place.
I hope this along with the other tips on this thread will help others setup a UITableView from scratch on a ViewController.
If you configured a tableView in IB you shouldn't also create one programmatically, you should create #property (nonatomic, retain) IBOutlet UITableView *tableView; and connect it to the tableView you configured in IB.
Try to set a breakpoint in the tableView's
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
delegate method to see if this method get called.
From Apple UITableView docs:
A UITableView object must have an object that acts as a data source
and an object that acts as a delegate; typically these objects are
either the application delegate or, more frequently, a custom
UITableViewController object. The data source must adopt the
UITableViewDataSource protocol and the delegate must adopt the
UITableViewDelegate protocol. The data source provides information
that UITableView needs to construct tables and manages the data model
when rows of a table are inserted, deleted, or reordered. The delegate
provides the cells used by tables and performs other tasks, such as
managing accessory views and selections.
As u can see if u don't set a dataSource to your tableView, the tableView will not know how and what to display, so nothing will happen.
You can set one by calling tableView.dataSource = self; or in IB drag from your tableView to the file's owner (that is your viewController that must implement the UITableViewDataSource Protocol)
There are two methods in the UITableViewDataSource protocol that your dataSource must implement:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
and
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath
If u won't implement those methods u will get a compiler warnings.
You can have more control on how the tableView will look if you implement the UITableViewDelegate protocol - like row/header/footer height, selections and more...
From Apple UITableView docs:
UITableView overrides the layoutSubviews method of UIView so that it
calls reloadData only when you create a new instance of UITableView or
when you assign a new data source. Reloading the table view clears
current state, including the current selection. However, if you
explicitly call reloadData, it clears this state and any subsequent
direct or indirect call to layoutSubviews does not trigger a reload.
ReloadData get called when the tableView is created or when you assign a new dataSource (or when you explicitly call it of course..).
This is when the tableView needs to know what to display (how many sections?, how many rows?, and which cell to display?) - So this is when numberOfRowsInSextion method called.
Like Eyal said, you shouldn't create a UITableView programmatically and in the Interface Builder. Instead, it is much easier to just create one in Interface Builder and assigns it's delegate and datasource properties to File's Owner in IB.
Once you've done this, you don't need to create one programmatically and there's no need for a #property for the tableview.
Instead, you could have your UIViewController's class files look like this:
// YourViewController.h
#import <UIKit/UIKit.h>
#interface YourViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
#property (strong, nonatomic) NSArray *yourData;
#end
Where the NSArray will contain your data that you will enter into the table programmatically. You may use other data classes too like an NSDictionary depending on what data you have and how you want it to sit in the table.
// YourViewController.m
#import "YourViewController.h"
#implementation YourViewController
#synthesize yourData;
- (void)viewDidLoad
{
[super viewDidLoad];
// Here you are creating some data to go in your table by inputting it as an array.
// I just used some basic strings as an example.
NSArray *array = [[NSArray alloc] initWithObjects:#"Data1", #"Data2", #"Data3", nil];
// Copying the array you just created to your data array for use in your table.
self.yourData = array;
}
- (void)viewDidUnload
{
[super viewDidUnload];
self.yourData = nil;
}
#pragma mark Table View Data Source Methods
// This will tell your UITableView how many rows you wish to have in each section.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [self.yourData count];
}
// This will tell your UITableView what data to put in which cells in your table.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifer = #"CellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifer];
// Using a cell identifier will allow your app to reuse cells as they come and go from the screen.
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifer];
}
// Deciding which data to put into this particular cell.
// If it the first row, the data input will be "Data1" from the array.
NSUInteger row = [indexPath row];
cell.textLabel.text = [yourData objectAtIndex:row];
return cell;
}
#end
This should just create a simple UITableView with three entries of data that you have entered programmatically.
If you have any problems or questions just post a comment. :)
Hope this helps.

Resources