I'm a newbie programming iOS and I've a problem adding a new cell to a UITableView object. I'm using an storyboard and one of the scenes is a UIViewController that has several subviews: textfields, a tableview, etc. I intend to add rows to this tableView from a detail scene.
I'm able to initially add rows to the table, but I'm not able to add a row afterwards. When I press a button to add the row I call the method '[self.stepsListTableView reloadData];' which produces a call to the method '- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section' and it returns a correct value, including the new array element. But method '- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath' is not called to update the table.
I do not understand what I'm doing wrong.
Details of my source code:
WorkoutDetailsViewController.h
(…)
#interface WorkoutDetailsViewController : UIViewController <StepDetailsViewControllerDelegate, UITextFieldDelegate, UITableViewDelegate, UITableViewDataSource>
#property (nonatomic, weak) id <WorkoutDetailsViewControllerDelegate> delegate;
#property (nonatomic, strong) Workout *workout;
(…)
#property (nonatomic, retain) IBOutlet UITableView *stepsListTableView;
(…)
WorkoutDetailsViewController.m
(…)
#synthesize stepsListTableView;
(…)
- (void)viewDidLoad
{
[super viewDidLoad];
addButton.enabled = FALSE;
workoutNameField.delegate = self;
if (self.workout == nil) {
self.workout = [[Workout alloc] init];
self.stepsListTableView = [[UITableView alloc] init];
}
self.stepsListTableView.delegate = self;
self.stepsListTableView.dataSource = self;
}
(…)
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
// Customize the number of rows in the table view.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
//return [self.workout.stepsList count];
NSInteger counter = 0;
counter = [self.workout.stepsList count];
return counter;
}
// Customize the appearance of table view cells.
- (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];
}
// Set up the cell...
// Pending
return cell;
}
- (void)stepDetailsViewControllerDidDone:(StepDetailsViewController *)controller
{
[self.workout.stepsList addObject:controller.step];
NSInteger counter = [self.workout.stepsList count];
[self.stepsListTableView beginUpdates];
NSArray *paths = [NSArray arrayWithObject:[NSIndexPath indexPathForRow:(counter-1) inSection:0]];
[self.stepsListTableView insertRowsAtIndexPaths:paths withRowAnimation:UITableViewRowAnimationTop];
[self.stepsListTableView endUpdates];
[self.stepsListTableView reloadData];
}
(…)
Also in the storyboard, I have setup the outlets delegate and dataSource to be the controller view.
Any idea ?
Regards,
JoanBa
I have solved the issue. I have discovered using debugger that method reloadData was called for a different UITableView than the one initialized in viewDidLoad.
I reviewed the UITableView settings in storyboard, which aparently were correct but I have deleted them and created again. Now it works.
In UIViewController header I have the line
#property (nonatomic, retain) IBOutlet UITableView *stepsListTableView;
and in implementation I have commented lines
//self.stepsListTableView.delegate = self;
//self.stepsListTableView.dataSource = self;
And, of course, in storyboard I have defined for the UITableView the following relationships:
Outlets: dataSource, delegate --> UIViewController
Referencing outlet: UITableView --> UIVIewController
That's it !!
You may have the table view set to static content. Select the UITableView in Storyboard and in the "Attributes Inspector" section of the menu on the right of screen select the "Content" field under the "Table View" header and set the value to "Dynamic Prototypes".
Screenshot for clarity:
This little trick caught me out several times when I was starting out.
Related
I try to search this problem in this site and I found this link How to insert items to a UITableView when a UIButton is clicked in iOS. But my problem is, I already copy the code on that link and It doesn't reload the data when I insert a value to my array.
here's the code "ViewController.m"
#import "ViewController.h"
#interface ViewController ()
#property(nonatomic,strong) NSMutableArray * array;
#property(nonatomic,weak) IBOutlet UITableView * tableView;
#end
#implementation ViewController
-(NSMutableArray *) array{
if(_array==nil){
_array=[[NSMutableArray alloc] init];
}
return _array;
}
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
- (IBAction)addInfo:(UIBarButtonItem *)sender {
[self.array addObject:#"sample"];
[self.tableView reloadData];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return [self.array count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *simpleTableIdentifier = #"TodoListItem";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier];
}
cell.textLabel.text = [self.array objectAtIndex:indexPath.row];
return cell;
}
#end
here's the code "ViewController.h"
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController <UITableViewDataSource,UITableViewDelegate>
#end
Is there something wrong with my code or there is something that I need to setup to my tableview? I'm very confuse of this and try to figure out the missing part of my code. I'm still studying the code of object c and I'm still noob for this. Please help me and thanks in advance.
How did you insert the UITableView?, on nib or programatically?, either way you need to flag the table's delegate and datasource, if on nib, right click on table and see that delegate and data source are connected to the files owner, if programatically check
self.MyTable.datasource = self
self.MyTable.delegate = self
please see how to check if connected [dataSource and delegate should be with a dot, if not click on circle and drag line to file's owner for both]
table delegate and datasource on interface builder "NIB"
Also please note that on this image outlet is not connected, you have to connected also to call the reload as you are doing now
edit, check if the button is connected?, put a log or a break point on ibaction for your button to know if is called
edit 2, you have to init the array, are you calling it? do this in view will appear or when you want to use it
edit 3, try this
- (IBAction)addInfo:(UIBarButtonItem *)sender {
if(self.array==nil){
self.array=[NSMutableArray array];
}
[self.array addObject:#"sample"];
[self.tableView reloadData];
}
I got 3 labels on my view displaying some stuff, a table view below (beginning at screen halfway down) and
a data retrieval method where I fill up an NSArray for the table view.
Everything works, the problem now is if there are more than a few elements in the array for the table view (more than the screen can display at once) I'm not able to scroll down to the bottom, it's always autoscrolling up again, as if the table view was empty space there (which it is not, I checked of course).
It worked on the previous screen the way I did it, I think the iOS doesn't notice the labels or something similar - it thinks the table view has the whole screen for itself. I tried resizing the table view in the xib to let it end just before the screen ends but the table view occupies all screen beginning from where it started. I also tried to
initWithFrame:CGRectMake...
But this did not work as well. Any ideas whats going on here? I'd be really glad about some help, thanks guys!
EDIT:
.h file
#interface ZProjekt : UIViewController <RetailHeaderTabSource, MFMailComposeViewControllerDelegate , UITableViewDelegate, UISearchDisplayDelegate, UISearchBarDelegate, UITableViewDataSource>
{
NSString *messageBody;
NSString *subjectBody;
NSMutableArray *fieldsInTable;
int fieldscounter;
}
#property (strong, nonatomic) IBOutlet UILabel *tableNamed;
#property (strong, nonatomic) IBOutlet UILabel *objectsInTable;
#property (strong, nonatomic) NSArray *DetailModal;
#property (strong, nonatomic) IBOutlet UILabel *mboSyncGroup;
#property (strong, nonatomic) IBOutlet UITableView *tableView;
#end
here is the code of the tableview .m file
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return fieldscounter;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
cell.textLabel.text = fieldsInTable[indexPath.row];
[Constants updateLabelValues:cell.textLabel withKey:k_IPhone_DetailCell_HeaderLabel];
return cell;
}
Try this one :
[tableView setContentOffset:CGPointMake(tableView.frame.size.width, tableView.frame.size.height) animated:NO];
or
on the TableViewController->Table View check the clip Subviews n Autoresize Subviews.
Also,
make sure the total rows are the same with the total contents in the tableView which is 3
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return 3;
}
Problem Found ! The problem is, the tableview doesn't know which table you are scrolling either the last one or the previous one. You need to re-assign the value to the NSMutableArray. You need to store the content of the array in the table1 into array or dictionary and you need to reassign in the CellForRowIndexPath. I had the same problem like this.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (tableView != table2.tblView) {
fieldsInTable = table1.fieldsInTable;
}
else
{
fieldsInTable = table2.fieldsInTable;
}
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
cell.textLabel.text = fieldsInTable[indexPath.row];
[Constants updateLabelValues:cell.textLabel withKey:k_IPhone_DetailCell_HeaderLabel];
return cell;
}
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.
How do I add a UITableView to an existing view?
I have added the control via interface builder, and added the correct delegates to the host view controller (including adding the table cell functions to the .m file)
But nothing gets called and nothing gets populated, the cellForRowAtIndexPath function, for example, never gets called.
in .h file
#interface GameViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
#property (weak, nonatomic) IBOutlet UITableView *friendsScoresView;
in .m file
init {
_friendsScoresView.delegate = self;
_friendsScoresView.dataSource = self;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// return number of rows
return 3;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"MyBasicCell"];
//NSMutableArray *nearbyScores = gclb->nearbyScores;
GCLeaderboardScore *playerScore = [gclb->nearbyScores objectAtIndex:indexPath.row];
cell.textLabel.text = [NSString stringWithFormat:#"%f",playerScore->score ];
cell.imageView.image = playerScore->photo;
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// handle table view selection
}
What am I missing? Also how do I tell the table to update / refresh its contents?
If you added the UITableViewController in IB then click on the outlets tab on the right and connect the DataSource and Delegate in IB. If you want to do it in code, then create an IBOutlet variable for your table and set the delegate and datasource property on your variable. Also, you can't do it on init on the UIViewController as the NIB has not yet been loaded. Do it on viewDidLoad.
I would like to display different content in its corresponding views by using only one subclass for generating a custom table view. When the table view is being filled with data, I would like to send the whole view to a specific view and display it.
When I generate the table view on the displaying view controller there is no problem. Even I can transfer the table view from the displaying view controller to another view, but when I try to generate the table view in another class and try to call it from the the root view controller, I only get a initialised but empty table view.
Here is the code:
tableViewGenerator.h
#import <UIKit/UIKit.h>
#interface TableViewGenerator : UIViewController <UITableViewDataSource, UITableViewDelegate>
- (UITableView *) getTableViewForOtherVC;
#property (nonatomic, strong) IBOutlet UITableView *spreadSheet;
#end
tableViewGenerator.m
...
- (UITableView *) getTableViewForOtherVC {
if (!self.spreadSheet) {
self.spreadSheet = [[UITableView alloc]initWithFrame:CGRectMake(0., 0., self.view.frame.size.width, self.view.frame.size.height/2.5) style:UITableViewStyleGrouped];
}
UITableView *tableViewToBeTransfered = self.spreadSheet;
return tableViewToBeTransfered;
}
...
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 1;
}
- (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 = #"Test";
return cell;
}
displayingVC.m
#import "DisplayingVC.h"
#import "TableViewGenerator.h"
#interface CTTOVViewController ()
#end
#implementation CTTOVViewController
- (void)viewDidLoad{
[super viewDidLoad];
UITableView *spreadSheet1;
if (!spreadSheet1) {
TableViewGenerator *tableVC = [[TableViewGenerator alloc]init];
spreadSheet1 = [tableVC getTableViewForOtherVC];
[self.view addSubview:spreadSheet1];
}
}
...
#end
I suppose that I have done something wrong on the table filling but I can not find what.
Any hint will be highly appreciated.
Thank you in advance
You don't seem to be setting the table view's dataSource and delegate properties anywhere, so you need to have these properly set.
On a separate front, what you're trying to accomplish is probably much easier if you generate a model, instead of the UITableView itself. I recommend checking out the models provided by free Sensible TableView framework as these can save you a huge amount of time.