iOS - cellForRowAtIndexPath not called on reloadData - ios

I know that this question have been asked so many times but unfortunately non of the solutions in those questions worked for me.
Here are some of the things I tried:
making sure that numberOfSectionsInTableView is not returning 0
making sure that numberOfRowsInSection is not returning 0.
insuring that reloadData is being called on the main thread by using performSelectorOnMainThread as shown below:
[self.tableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:NO]
setting the delegate in viewDidLoad, as shown below:
(void)viewDidLoad {
[super viewDidLoad];
// .....
self.tableView.dataSource = self;
self.tableView.delegate = self;
}
I'm creating an iOS app that keeps track of events. The app's main view is a UITableViewController (Calling it main list) that is embedded into a UINavigationController which is embedded into a UITabViewController. The user can create two objects a list or an event. When a new list is created and selected from the current UITableViewController, a new UITableViewController is pushed into the same UINavigationController.
Each list (UITableViewController) has an edit button that allows the user to delete the list.
Assuming that the user chooses to delete "list 3" that belongs to the main list, that is there are 3 view controllers pushed to the UINavigationController:
UITableViewController for the main list
UITableViewController for the selected list "list 3"
UITableViewController for the edit list view
when the delete button is tapped on, an alert is shown. If the user chooses to proceed the following code gets executed:
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
// .....
//here I'm calling unwind which is going to pop the UITableViewController
//for the edit table view
[self performSegueWithIdentifier:#"unwindFromDelete" sender:nil];
//here I'm calling a method i wrote to pop the UITableViewController
//of the deleted list (in this example "list 3")
[self setNavigationControllerViewControllers];
// .....
}
- (IBAction)unwindFromEditListToList:(UIStoryboardSegue *)segue {
.....
}
- (void)setNavigationControllerViewControllers {
NSMutableArray *viewControllers = [[self.navigationController viewControllers] mutableCopy];
[viewControllers removeLastObject];
[self.navigationController setViewControllers:viewControllers];
}
After that code is executed, viewWillAppear for the main list UITableViewController is executed as shown below:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// ..... update the data
[self.tableView reloadData];
}
[self.tableView reloadData] is going to cause the following code to be executed:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// .....
return someValue; // Something greater than zero
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
// .....
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// .....
return someValue; //Something greater than zero
}
#pragma warning "Not getting called"
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// .....
}
All the methods are executed but cellForRowAtIndexPath is not. The problem is gone if setNavigationControllerViewControllers mentioned above is not called. Not calling setNavigationControllerViewControllers will result in UITableViewController for "list 3" to show which is bad since "list 3" is no longer stored in the core data I'm using to store the data.
If you have any idea what to do, please let me know.
Thanks

numberOfSectionsInTableView must return at least 1.
See this link below.
You don't even need to override it if you're not using multiple sections.

If the height of table is dynamic. Make sure the table is in a view, visible and height is
atleast 1px when reload is called.

I put an answer on this because I've been browsing for a solution for sometime and this question is the closest to the one I was asking myself...
It turns out that I was using auto-layout with visual format.
Seems that UITableView and NSLayoutContraint don't play well together. As soon as I commented the method which apply constraints, the cellForRowAtIndexPath was called and the tableview displayed...

Please put debug point on this,
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
method and log someValue value may it return 0 that's why cellForRowAtIndexPath method not called.
Thanks.

Don't know who down voted Avalerion answer, but his statement is important too.
I had same problem, delegate method cellForRowAtIndexPath was not called though the following conditions were matched:
Delegate and Datasource was set
numberOfSectionsInTableView method was returning 1
numberOfRowsInSection returned correct number and data was available.
Reason:
I didn't set height constraint for the table, so it had no height, thus app didn't call cellForRowAtIndexPath even though it called eg numberOfRowsInSection.

Related

Calling methods when UITableViewCell is pressed

I would like to call a method when a UITableViewCell is selected/tapped. I could do it easily with a static table view, but it requires a UITableViewController which is not good for me in this case, therefore I'm using a normal vc.
I have 10 specified methods like this:
- (void) methodOne {
NSLog(#"Do something");
}
- (void) methodTwo {
NSLog(#"Do something");
}
....
And I would like to call the methodOne when the first cell was tapped, call the methodTwo when the second cell was tapped and so on..
As a first step I set the numberOfRowsInSection to return 10 cells, but have no idea how could I connect the selected cells with the methods. Is there any quick way to do it? It would be a dirty solution to create 10 custom cells and set the every method manually for the custom cells, and there is no free place for it.
You can create an array of NSStrings with method names in the order they should be called from their corresponding UITableViewCells.
NSArray *selStringsArr = #[#"firstMethod", #"secondMethod", #"thirdMethod];
Then create a selector in tableView:didSelectRowAtIndexPath: from the strings array and call it using performSelector:.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *selString = selStringsArr[indexPath.row];
SEL selector = NSSelectorFromString(selString);
if ([self respondsToSelector:#selector(selector)]) {
[self performSelector:#selector(selector)];
}
}
Of course, there are some limitations to using performSelector: which you can read here.
You can use this method for whenever any cell is tapped on the table view
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSInteger selectedRow = indexPath.row; //this is the number row that was selected
switch (selectedRow)
{
case 0:
[self methodOne];
break;
default:
break;
}
}
Use selectedRow to identify which row number was selected. If the first row was selected, selectedRow will be 0.
Don't forget to set the table view's delegate to your view controller. The view controller also has to conform to the UITableViewDelegate protocol.
#interface YourViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
As long as the table view has a data source and a delegate, it doesn't matter what kind of view controller it is on. All a UITableViewController really is is a UIViewController that already has a table view on it and is that table view's delegate and data source.

Delay table view loading or load array before viewDidLoad?

I'm using a webservice to fill an array of items, which then are used to fill cells of my table view.
Now i'm having an issue with the table view itself. When I check the " numberofrows" method, the array isn't loaded yet. For some reason it loads "right after" that (according to my nslogs & breakpoints), even though i've put every loading method as early as i could in that controller.
Now what i don't know is :
is there a way to delay the table view creation so i'm sure my array is loaded?
is there a way to load my array earlier? It's currently my first line in the viewDidLoad. (calling another class, which then calls the webservice and returns an array, but by then, the table view is already loaded and empty).
What i've tried : Putting a tableview reloadData. But it simply doesn't work. For some reason the compiler reads the line but doesn't load anything new, even though at that point the array is full.
Am i missing something?
(My tableview works just fine if I put hardcoded objects in my array)
I can edit and add some code that you guys would request, but since this looks like a school problem here, maybe i just forgot something.
Guys, i'm all ears !
Edit :
My different methods ; i've removed unecessary code for clearer reading.
The compiler NEVER goes in the cellForRow because numberOfRows is returning a zero number.
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"CustomCellTableViewCell";
CustomCellTableViewCell *cell = [self.tbUpcomingEvents dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
cell.lbTitle = [_eventList objectAtIndex:indexPath.row];
return cell;
}
number of rows ; the nslog returns zero.
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSLog(#"numOfRows : %i", [_eventList count]);
return [_eventList count];
}
my webservice method, this is the method calle from the "webservice class" to load the data.
Here, the NSLog shows a full array.
- (void)loadData:(NSMutableArray*)arrayEvent
{
//arrayEvent is full from the internet data. eventList is also full on the NSLog.
_eventList = arrayEvent;
NSLog(#"LoadData : %i", [_eventList count]);
[self.tbUpcomingEvents reloadData];
}
my viewdidLoad method
- (void)viewDidLoad
{
[super viewDidLoad];
//Calling my UseCaseController to load the data from the internet.
UCC *sharedUCC = [UCC sharedUCC];
[sharedUCC getUserEventsInDbDis:self :_user];
}
As you said you are using webservices. So it is executing your code in the block which means it is running in separate thread. Now as per apple documentation UI updates should happen in main thread. So for table creation you need to include the same in below GCD code:-
dispatch_async(dispatch_get_main_queue,()^{
// write your code for table creation here
});

UITableview doesn't call cellForRowAtIndexPath

The first time the view controller is pushed (from the previous view controller) all the delegate methods are called (inside a navigation controller).
When pressing back to return to the previous view controller , and then pushing it again (for the second time)
cellForRowAtIndexPath isn't called but numberOfRowsInSection and numberOfSectionsInTableView are called.
The reloadData is called within
-(void)viewWillAppear:(BOOL)animated
{
[self.tableView reloadData];
}
and I have tried in
-(void)viewDidAppear:(BOOL)animated
{
[self.tableView reloadData];
}
and it doesn't help.
Edit
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 1; // called
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 3; // called
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// relevant code for cell - THIS METHOD IS NOT CALLED SECOND TIME
}
If you don't find any valid reason as explained above I will highlight
another mistake one can make is (as I once did).
The steps to note are
1) initialise your custom table view as first step
2) set the delegate and datasource before you add the tableView to you view.
If one has done these steps mistakenly after adding to the tableview to your self.view object, then it would be a silent way to make yourself struggle.Correct order is as under:
self.myTableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, myViewwidth, 120) style:UITableViewStylePlain];
self.myTableView.tag = MY_TABLEVIEW_TAG;
self.myTableView.delegate = self;
self.myTableView.dataSource = self;
[self.view addSubview:self.myTableView];
This always happens when you define your underlying data source (array or Dictionary) as weak, the first time it gets pushed, the data is there, when deallocated, it will release and you lose control over it.
Double check the weak/strong condition and optionally set the data source before pushing again.
Please check if you have set/connected Delegate and Datasource to the File's Owner.
And check the array count of your model, if it contains value of not?
NSLog in the numberOfRowsInSection method and check it by using breakpoints and step over.

Uitableview cell changes value when scrolling

The code below is creating a search for many strings. Initially there are 5 rows, when you reach row five, it adds another row. Instead of just directly editing the row, i load a filter controller (another view controller that as you type it completes words for you). When the user finishes finding a word he clicks it and comes back to this view controller. Now i want to fill the cell that was originally tapped with the text from the filter.
I tried asking earlier and didn't get any concrete answers.
I am running into a problem where when i scroll (after adding a new row), it starts filling in those rows with info already in the table, (as opposed to staying blank)
Please help me where i am going wrong
//global indexpath to remember which cell tapped
NSIndexPath *globalPath;
#interface SearchViewController ()
#end
#implementation SearchViewController
//Load implementation once per launch
- (void)viewDidLoad
{
[super viewDidLoad];
[self linkInputTableToDelegate];
_temporaryResultsArray =[[NSMutableArray alloc]init];
_flurryArray=[[NSMutableArray alloc]init];
_numberOfSections=6;
}
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:NO];
[InputTable reloadData];
textFromUserDefaults=[[[HelperMethods alloc]init]getObjectUserDefault:#"textFiltered"];
[self addTextToFlurryArrayForFlurryAndSavedLists:_textFromUserDefaults];
}
-(void)viewDidDisappear:(BOOL)animated{
}
- (IBAction)searchButtonPressed:(UIButton *)sender {
self.tabBarController.selectedIndex = 1;
}
//Makes the input table respond to delegate table view methods
-(void)linkInputTableToDelegate{
_inputTable.dataSource=self;
_inputTable.delegate=self;
}
-(void)performSearch:(NSString*)text{
//do search
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
int numberOfRows=_numberOfSections;
//Rows for iPhone 4
if ([[UIScreen mainScreen]bounds].size.height==480) {
numberOfRows=numberOfRows;
//Rows for iPhone 5
}else if ([[UIScreen mainScreen]bounds].size.height==568){
numberOfRows=numberOfRows+1;
}
return numberOfRows;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
//In reality groups are created with 1 row inside, this is to allow spacing between the rows
return 1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *kCellID = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kCellID];
if (!cell) {
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kCellID];
}
//Is the cell the same as the one clicked when going to ingredient filter
BOOL cellIndexPathSameAsSelected=[self isCellIndexSameAsPreviousClicked:indexPath];
cell.textLabel.textColor=[UIColor blackColor];
if (cellIndexPathSameAsSelected && _textFromUserDefaults!=nil) {
if (![cell.textLabel.text isEqualToString:_textFromUserDefaults]) {
cell.textLabel.text=_textFromUserDefaults;
[self performTextSearch:_textFromUserDefaults];
}
}
return cell;
}
//Compares the previous clicked cell with the cell now selected
-(BOOL)isCellIndexSameAsPreviousClicked: (NSIndexPath*)cellPath{
if (cellPath.row == globalPath.row && globalPath.section==cellPath.section) {
return YES;
}
else{
return NO;
}
}
- (void)updateTableViewWithExtraRow :(NSIndexPath*)rowSelected{
NSLog(#"number of sections =%i",_numberOfSections);
if (rowSelected.section == _numberOfSections) {
_numberOfSections ++;
}
}
#pragma mark - Table view delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *cellText = [tableView cellForRowAtIndexPath:indexPath].textLabel.text;
[[[HelperMethods alloc]init]saveObjectToUserDefaults:cellText :#"textFiltered"];
globalPath = indexPath;
[self updateTableViewWithExtraRow:indexPath];
}
-(void)addTextToFlurryArrayForFlurryAndSavedLists:(NSString*)text{
if ([_flurryArray count]==0 &&[text length]>0) {
[_flurryArray addObject:text];
}
for (int i=0;i<[_flurryArray count];i++) {
NSString *textInArray=[_flurryArray objectAtIndex:i];
if (![textInArray isEqualToString:text]) {
[_flurryArray addObject:text];
}
}
NSLog(#"Total number of saved items = %i",[_flurryArray count]);
}
// Dispose of any resources that can be recreated.
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
I have a couple of reactions looking at the code:
A couple of observations about the proper use of the UITableViewDataSource methods, specifically numberOfRowsInSection, numberOfSectionsInTableView, and cellForRowAtIndexPath:
These really should be driven by some model data structure (e.g. a NSMutableArray) and nothing else;
These methods should be stateless. They should not relying on the value of some NSString instance variable, like _textFromUserDefaults) but rather always look up the value in the NSMutableArray model structure on the basis of the value of the indexPath parameter. You simply cannot make any assumptions about when cellForRowAtIndexPath will be called. This may well account for your duplicate values.
None of these should be doing anything besides responding to the UITableView inquiry. For example, your cellForRowAtIndexPath is invoking performTextSearch. It really shouldn't do anything except return the cell.
Your cellForRowAtIndexPath currently has conditional logic and only updates the cell if certain conditions holds. Because cells are reused, you really want to make sure that you initialize the cells regardless. You can't be assured that the cell is blank when you get it, nor that the previous contents are the previous values for that indexPath. Because cells are reused, it could be for an entirely different row. This could also account for your duplicative entries.
Regarding the interaction of the master view controller and the details view controller, there are more elegant ways than passing data back and forth via NSUserDefaults. For example when you initiate the details view controller, you could just pass it the information it needs. And when it's done, it should call a method in the master view controller to update the data in the master view. To do that, the master view controller should conform to some protocol of your own creation. If you see the example I shared via chat, you can see what that might look like. Anyway, by having some delegate method in the master view controller that the detail view controller calls when it's done, that eliminates the rather fragile technique of using viewDidAppear to control the updating of the master table view.
You might want to contemplate employing "edit" (which allows you to delete, possibly also edit a particular row) and "add" buttons like the standard "master-detail" template that Xcode provides. There are a number of standard conventions here that might be better than having an array of blank cells that you can then tap on. Clearly, your user experience is entirely up to you, but you can always contemplate whether there are existing, familiar conventions that you might employ.
Rob's feedback is good. In broader terms, you can't rely on the cells in a UITableView to hold onto their data. For efficiency, it will be creating, using, and destroying cells at will, and using cellForRowAtIndexPath to figure out what they should look like. Instead of testing what's in a cell, you need to have your own set of data which describe the value of each cell, and just set the value based on the indexPath. I'd recommend storing all your cell information in an NSMutableArray which contains NSStrings or something more complicated if necessary. It will be easy to set default values when you add cells to the array. Then cellForRowAtIndexPath can just access the array rather than attempting its own logic based on current cells.

UITableView in UIViewController reloadData not updating table view

I have my MainViewController which a UIViewController class, in this view I have a table view and another view embedded in it. As you can see I added the table view delegates to the view controller.
#interface MainViewController : UIViewController <FlipsideViewControllerDelegate, UITableViewDataSource, UITableViewDelegate> {
Now to my question which has been asked about 50 times from multiple searches on the internet but still I have not been able to find a solution.
Why doesn't reloadData work...?
I am downloading info from an XML feed and I refresh the feed from the app delegate:
- (void)applicationDidBecomeActive:(UIApplication *)application
I have inserted multiple NSLogs, this is what I have found. The refreshed data from the XML feed is coming in no problem. But the table view cells are not being refreshed.
I placed a NSLog in:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
Although the data is being refreshed, when I call [tableView reloadData]; the cellForRowAtIndexPath is never called.
I am hoping someone can help me with this issue, I have been working on this issue for about 12 hours with no luck. I have exhausted the search button on multiple sites....
FYI:
-Table View is wired in Interface Builder.
-My tables are being loaded one by one via TableViewCells, following Apples example starting on page 51 of the Table View Programming guide.
Additional Code:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return 17;
}
Thanks for the help
Do you have any example Code ?
My first ideas:
Datasource not connected
Rows in Section give 0 back
Sections give 0 back
Everything is working now... That was a lot of time wasted on my part...
- (void)applicationWillEnterForeground:(UIApplication *)application
{
[_mainViewController refresh];
[_mainViewController.tableView reloadData];
}
- (void)refresh {
Parser *xmlParser = [[Parser alloc] init];
[xmlParser parseRssFeed:#"http://www.somesite.com/file.xml" withDelegate:self];
[xmlParser release];
[self.tableView reloadData];
}

Resources