Objective-c UITableView separate class cellForRowAtIndexPath does not get called - ios

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

Related

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

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.

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.

How to create a Common Custom TableView Class?

I want to create a custom tableView class, Which can be used in any view Controller. I have to just create tableview object and set an array and frame of tableview . Then this tableview will be add as subview on my view. and also give me a click event.
I just want to avoid writing tableview datasource and delegate method in every viewController class.
Take a viewController or tableviewController class and code all the delegates and data source methods there. now in you view controller where you want to make it as a subview call the tableview class and add it as a subview.
EX:
TableviewContrller *libaray =[TableviewContrller new];
[libaray willMoveToParentViewController:self];
[self.view addSubview:libaray.view];
[self addChildViewController:libaray];
To hide write this code in your tableview controller class
[self.view removeFromSuperView];
As you are using a reusable class you need to send the array information to that class. along with it it will be better to send either class name or setting tag value to tableview
So in your tableview class write this
-(id)initWithInformationArray :(NSMutableArray *)dataArray andTagValueforTableview :(int) tagValue
{
self = [super init];
if (self != nil)
{
NSLog(#"%#", dataArray);
}
return self;
}
Now sub viewing will be like this
TableviewContrller *libaray =[[TableviewContrller alloc]initWithInformationArray:YOURARRAY andTagValueforTableview:TAGVALUE];
[libaray willMoveToParentViewController:self];
[self.view addSubview:libaray.view];
[self addChildViewController:libaray];
Hope this will help.
May be you can use UITableViewController.
UITableViewController is a subclass of UIViewController, when you create a subclass of UITableViewController, the template has the usual methods of tableview datasource and delegate methods.
You'll need to create a custom class and create your own delegate in that class for UITableView. Now whenever you create a UITableView assign that custom class as the class for UITableView.
If you don't know how to create custom delegates then check below links:
http://www.alexefish.com/post/522641eb31fa2a0015000002
http://ios-blog.co.uk/tutorials/quick-tip-create-your-own-objective-c-delegate-protocol/
Hope this will help you :)
You can create BaseTableView class.
#interface BaseTableView : UITableView <UITableViewDelegate, UITableViewDataSource>
{
NSArray* listObject;
}
#property (nonatomic, retain) NSArray *listObject;
-(id) initWithFrame:(CGRect)frame style:(UITableViewStyle)style;
#end
#implementation BaseTable
#synthesize listObject;
-(id) initWithFrame:(CGRect)frame style:(UITableViewStyle)style
{
if(self = [super initWithFrame:frame style:style])
{
self.dataSource = self;
self.delegate = self;
}
return self;
}
-(void)setListObject:(NSArray *)listObjectRef
{
[listObject release];
listObject = [listObjectRef retain];
[self reloadData];
}
-(void) dealloc
{
[listObject release];
[super dealloc];
}
#end
Inherit this class for specific use and override following methods according to needs
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
In your ViewController class use following code
SpecificTableView *table = [[SpecificTableView alloc] init];
[table setListObject:((FRFTReportList*)obj)];
Hopefully this will help.

Table view cell properties remain nil

I am trying to implement the concept shown in this example project. My goal is to separate my view controller class and the datasource protocol. Instead of implementing the table view datasource methods in my table view controller class, I try to put it in its own class, and in my view controller, I only call this method to set up my table view:
- (void)setupTableView
{
void (^configureCell)(JVRTodoItemCell *, JVRTodoItem *) = ^(JVRTodoItemCell *cell, JVRTodoItem *todoItem)
{
[cell configureForTodoItem:todoItem];
};
NSArray *todoItems = currentUser.todoItems;
self.todoArrayDataSource = [[JVRArrayDataSource alloc] initWithItems:todoItems withCellIdentifier:TodoCellIdentifier withConfigureCellBlock:configureCell];
self.tableView.dataSource = self.todoArrayDataSource;
[self.tableView registerClass:[JVRTodoItemCell class] forCellReuseIdentifier:TodoCellIdentifier];
}
The data source is separated into its own class:
#interface JVRArrayDataSource ()
#property (copy,nonatomic) NSArray *items;
#property (copy,nonatomic) NSString *cellIdentifier;
#property (copy,nonatomic) void (^configureCellBlock)(id item, id cell);
#end
#implementation JVRArrayDataSource
...
#pragma mark - UITableViewDataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.items.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:self.cellIdentifier forIndexPath:indexPath];
id item = [self itemAtIndexPath:indexPath];
self.configureCellBlock(cell,item);
return cell;
}
The interesting part is that creating the cell based on the identifier (using dequeueReusableCellWithIdentifier:forIndexPath:) seems to be successful, because the correct cell gets allocated, but its labels remain nil. I try setting up my cell using the following method, but the values remain nil (aTodoItem has valid properties):
- (void)configureForTodoItem:(JVRTodoItem *)aTodoItem
{
self.todoItemTitle.text = aTodoItem.title;
self.todoItemPriority.text = [NSString stringWithFormat:#"%d", aTodoItem.priority];
}
I am trying to figure out what could possibly be missing here, but so far, I haven't managed to fix the issue yet, and I'm starting to lose hope. Any help would be appreciated.
UPDATE:
To make it clear, the issue is shown on this picture.
It seems that the cells get created, but its labels don't.
If all you want to do is separate your tableview datasource delegate from the view controller you can create a separate class called TableViewDataSource. Within that class you can manage the datasources and their table view cells; configuring the them in your view controller, but letting the TableViewDataSource manage them.
TDSTableViewDataSource.h
#import <Foundation/Foundation.h>
#protocol TDSTableViewDataSourceDelegate <NSObject>
- (NSString *)fetchCellIdentifierForObject:(id)object;
- (UITableViewCell *)configureCell:(UITableViewCell *)cell usingObject:(id)item;
#end
#interface TDSTableViewDataSource : NSObject <UITableViewDataSource>
#property (strong, nonatomic) NSArray *items;
#property (strong, nonatomic) id<TDSTableViewDataSourceDelegate> delegate;
#end
TableViewDataSource.m
#import "TDSTableViewDataSource.h"
#implementation TDSTableViewDataSource
- (NSArray *)items {
if (!_items) _items = [[NSArray alloc] init];
return _items;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
if ([self.items count]) {
return [self.items count];
} else {
NSLog(#"numberOfSectionsInTableView could not be determined. self.items is nil or empty.");
return 0;
}
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if ([self.items count]) {
return [self.items count];
} else {
NSLog(#"numberOfRowsInSection could not be determined. self.items contains fewer section requested does not contain any items.");
return 0;
}
}
/*
Single dimension Array of items belonging to a UITableView section
The method checks if the cell implements the HZConfigureTableViewCellDelegate, which is required.
The delegate should be the View Controller.
*/
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
id obj = [self.items objectAtIndex:indexPath.row];
UITableViewCell *cell = nil;
if ([self.delegate conformsToProtocol:#protocol(TDSTableViewDataSourceDelegate)]) {
NSString *cellIdentifier = [self.delegate fetchCellIdentifierForObject:obj];
cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (obj)
cell = [self.delegate configureCell:cell usingObject:obj];
}
return cell;
}
#end
This class and the protocol will essentially allow you to fetch and configure UITableViewCell's and not have to implement the protocols into your View Controller.
Inside your view controller, you create a datasource property using the protocol above.
#import "TDSViewController.h"
#import "TDSTableViewDataSource.h"
#interface TDSViewController () <UITableViewDelegate, TDSTableViewDataSourceDelegate>
#property (strong, nonatomic) TDSTableViewDataSource *dataSource; // UITableView data source.
#property (weak, nonatomic) IBOutlet UITableView *tableView;
#end
#implementation TDSViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.tableView.dataSource = self.dataSource;
self.dataSource.delegate = self;
}
#pragma mark - UITableView methods
-(NSString *)fetchCellIdentifierForObject:(id)object {
// Check if this is an event or a Reminder class.
if ([object isKindOfClass:[UITableViewCell class]]) {
// Return the cell identifier for this particular cell.
return #"com.myapp.defaultcell";
}
return #"blankcell";
}
- (UITableViewCell *)configureCell:(UITableViewCell *)cell usingObject:(id)item {
UITableViewCell *configuredCell = cell;
// Check if this is an event or a reminder.
if ([item isKindOfClass:[UITableViewCell class]]) {
// Configure the cell to present what data we want here...
}
return configuredCell;
}
#end
This is a complete example project. You can use this to configure any kind of cell you want, without having to add the datasource methods to your view controllers.
The view controller is used by the ConfigureTableViewCellDelegate protocol to configure the UITableViewCell's and use them in the Table View. Since the code is segregated now, the TableViewDataSource class now handles presenting the data to the table view. The View Controller is simply used to configure the cell's. This allows you to use custom UITableViewCells' on each ViewController if you want, and not have to deal with implementing the data sources each time.
UPDATED
Provided a better example, a complete project template.
In the ViewDidLoad register the nib, it fix the problem :)
-(void)viewDidLoad
{
[self.leftTableView registerNib:[UINib nibWithNibName:NIB_FILE bundle:nil] forCellReuseIdentifier:CELL_IDENTIFIER];
}
After hours of digging, I've managed to solve the issue (for now), by changing my custom cell's outlets to strong properties, and initializing them in the cell's init method:
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
self.todoItemTitle = [[UILabel alloc] init];
self.todoItemPriority = [[UILabel alloc] init];
}
return self;
}
This is very strange, since I thought that creating my views in storyboard, this should be automatically taken care of, and I've never had to do this manually before.

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

Resources