80

My app has two states: logged in and not logged in, and I have the following architecture (vastly simplified):
- ViewController A which contains a search box and a table view.
- ViewController B which is used for logging in the app.

The flow is the following:
- the user is not logged in;
- A is pushed on the stack. In viewWillAppear I check if the user is logged in and if yes, an async network request is being made and, once that is done, the table is loaded with the data from the network. However since the user is not logged in at this point, the table view is empty;
- the user taps on the search box; because he's not logged in, B is pushed (after a confirmation);
- he logs in successfully in B, then presses a button which pops B and shows A again;
- at this point in time, because he's logged in, from viewWillAppear I do the async request;
- when that is completed, I call reloadData on the table view.

What I notice is that numberOfRowsInSection: is called and it returns the correct result, however cellForRowAtIndexPath: is NOT called afterwards and the table remains empty.

I've checked and reloadData is called on the main thread.

Any idea what can it be? Cause it's driving me nuts!

Thanks,
S.

EDIT: Here's the async bit of code from viewWillAppear in A.

if ([User isLoggedIn]) {
    [self.asyncRequest fetchDataWithCompletionHandler:^(id response, NSError *error) {
        [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
        if (error) {
            [Utils displayError:error];
        } else {
            self.array = response;

            self.isLoaded = YES;
            [self.tableView reloadData];
            [self.tableView setContentOffset:CGPointMake(0.0f, 0.0f) animated:NO];
        }
    }];
}

I've checked that the async request completes successfully and that response contains the correct data (this is the array used to back the UITableView).

After reloadData, I've put a breakpoint in tableView:numberOfRowsInSection: and it stops there and it returns the correct number of elements in array. After that, however, the breakpoint in tableView:cellForRowAtIndexPath: is never hit.

Stelian Iancu
  • 2,462
  • 3
  • 18
  • 28
  • The correct method selector is `tableView:cellForRowAtIndexPath:` not `cellForRowAtIndexPath:`. – Ole Begemann Oct 10 '11 at 11:57
  • If numberOfRowsInSection: is returning correct data, I think there might be problem with setting the delegate and datasource. Can you post some code?? – Shri Oct 10 '11 at 12:06
  • Yeah, I know, it's that one. I've pasted here the short version. – Stelian Iancu Oct 10 '11 at 12:07
  • The delegate and the data source are set in Interface Builder, to the File's Owner (i.e. A). – Stelian Iancu Oct 10 '11 at 12:08
  • Also, another point: this works well afterwards, i.e. if, after I log in, I switch away from A, then I come back to it, the table gets populated correctly. Only when I return to it from B doesn't work. – Stelian Iancu Oct 10 '11 at 12:08
  • Yes, it is. If I push A from another VC then it works correctly. So the problem only appears when I have: A (empty cause the user is not logged in) -> B (user logs in) -> A again, as B was popped. – Stelian Iancu Oct 11 '11 at 08:29

24 Answers24

295

One valid scenario for why numberOfRowsInSection would be called but cellForRowAtIndexPath would not be called is when the size or positioning of the table does not require any rows to be displayed.

For example, let's say you had a table that was 20 pixels high, but the header for the first section was 30 high and you returned nil for the header (or did not implement viewForHeaderInSection). In this case, no rows would be displayed and it would look like the table just isn't there.

I see you are using IB. The sizing of the table can be deceptive in IB as it is assuming header and footer sizes. I would log the frame for the table so you understand where it is when the appliction is run (versus where it appears in IB). I would also be sure to manually size and position the table's frame before calling reloadData. This will solve this valid case where cellForRowAtIndexPath is not called.

Joel
  • 15,654
  • 5
  • 37
  • 60
  • 74
    This guy knows what he's talking about. – akaru Dec 25 '12 at 20:03
  • What a great an answer here, @joel just gave a very good explaination. I actually ran into a similar issue and I took almost one day for debugging. In my case, the cellForRowAtIndexPath func won't be called. With this answer, I found out the AutoLayout has calculated height for table to be 0. And that's the root cause, App determined not to render the cell at all. – Leon Guan Jul 31 '14 at 01:21
  • 2
    I'm so sick of letting Xcode suggest the constraints for views. My table view never showed up because of some $#!tty constraints it suggested. Changed the constraints and everything worked perfectly. – RyJ Sep 23 '14 at 18:08
  • I had a slight variation on this. I placed a UITavleView with IB without using auto-layout. The frame of the table view was set to automatically stretch to the height of the cell. In the .m file I set the height of the frame of the cell, and the height of the frame of the table view. XCode ignored my code set frame the first time it displayed the table view, but respected the setFrame call the second time. When I disabled auto-stretch in the Y axis my problem went away and the setFrame methods worked as expected. XCode constraints are painful some days. Thanks for this answer! – JasonD Dec 05 '14 at 07:15
  • how can the header be 30 but then you returned `nil` for it? Are you talking about if one has implemented `heightForHeaderInSection` and he's returning 30 but then for the `viewForHeaderInSection` has returned `nil`? – mfaani May 09 '17 at 16:19
  • @Honey yes, that's the example given. However there are numerous reasons why the table might not have any rows to display based on the positioning and sizing. – Joel May 10 '17 at 17:23
  • Let's say: you had a table that was 20 pixels high, but the header for the first section was 30 high and you **returned a view** for the header – mfaani Nov 14 '18 at 15:19
  • Totally on spot. My tableView had height 0. Saved me a good headache! – Eneko Alonso Mar 05 '19 at 05:37
  • Hint to log your tableView's frame: `NSLog(@"tableView size: %@", NSStringFromCGSize(self.tableView.frame.size));` For me it turned out that I had wrong constraints set. The size told me it's correct. – Bruno Bieri May 16 '19 at 06:15
  • For me, it was in a vertical UIStackView and had width 0. Putting equal widths with the stack-view solved it. I wish I found this answer sooner! – kodu Aug 01 '19 at 12:38
  • Thank you so much for your answer. I have set the tableview height 0 for some reason. Then my cellforrow never called. I was worry about this. but This answer helped me out. – Saravanan Aug 06 '19 at 10:02
45

Check that the numberOfSectionsInTableView is not returning 0.

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    // Return the number of sections.
    return 1;
}
Kuldeep
  • 4,466
  • 8
  • 32
  • 59
Peter Kelly
  • 14,253
  • 6
  • 54
  • 63
  • 2
    I'm experiencing the same problem as the original author. This method is returning 1, as shown in your answer, and is being called... yet cellForRowAtIndexPath is not called. The datasource and delegate are set to the file's owner through Interface Builder; I've even tried setting it by code (tableView.delegate = self) and still it's not called. Just thought I'd contribute these factors since the author hasn't accepted an answer but you seem to be on the right track. Hope someone can figure this out! – Danny Dec 09 '12 at 10:42
  • 7
    According to Apple "The default value is 1." This method isn't needed. – Peter DeWeese May 28 '13 at 19:30
  • 4
    It absolutely was needed at the time of answering this question. – Peter Kelly Jun 07 '13 at 12:36
  • I can confirm that this method returned 0 and was causing trouble. I've implemented it to return 1 in my table view controller and everything was fixed. – Can Poyrazoğlu Apr 12 '17 at 11:31
  • Its returning 3 for me. – ScottyBlades Sep 25 '18 at 04:29
28

I had the exact same issue and the problem was that I was trying to call reloadData: from another thread. The solution would be:

dispatch_async(dispatch_get_main_queue(), ^{
    [self.tableView reloadData];
});
Nikolay Dyankov
  • 6,491
  • 11
  • 58
  • 79
18

If you are using a different dataSource, as I was, make sure you are retaining the dataSource. Merely instantiating the class that will be the dataSource and assigning it via tableView.dataSource = myDataClass will not be sufficient as the dataSource property of a tableView is weak and will be released when viewDidLoad completes. All of the other methods were being called for me — even, surprisingly, heightForRowAtIndexPath — so this took me some time to debug.

avance
  • 1,993
  • 2
  • 21
  • 23
13

If a Table View inside a view that conflicts something like Scrool View, it does not called. You should separate the views in your Storyboard or *.xib file.

// True
    ▼ View
        ► Table View
        ► Scrool View
        ► Constraints

// False
    ▼ View
        ► Scrool View
            ► Table View
        ► Constraints
behicsakar
  • 220
  • 2
  • 9
12
// For others showing up on this questions via Google, etc.
// Check and make sure you've set the table view's data source and delegate.
self.tableView.dataSource = self;
self.tableView.delegate = self;
John Erck
  • 9,478
  • 8
  • 61
  • 71
8

We had the same/similar issue. The code reached numberOfSections and numberOfRowsInSection (and it even returned values) but could not reach cellForRowAt. At the time, Table View in the Storyboard had only constraints for right side, left side and top, but not for bottom. Once we added constraint to the bottom, it worked like a charm.

So, check that you provide all needed constraints for the Table View.

6

If you are using auto layout like me and adding tableview to your view controller's view make sure you have added this line when you are allocating your table view.

tableView?.translatesAutoresizingMaskIntoConstraints = false

Silly mistake from my side.

Anuran Barman
  • 1,556
  • 2
  • 16
  • 31
4

Everyone keeps talking about height, but my TableView in a StackView with leading alignment ended up with 0 width.

Make sure to check if your TableView is correct size using Debug View Hierarchy.

Yaroslav
  • 2,435
  • 1
  • 19
  • 36
  • and what was your solution? Mine is inside a stackView, but still I don't see it the debugger – mfaani Oct 10 '17 at 19:27
  • @Honey you might want to set its height constraint to a constant and disable scrolling. – Yaroslav Oct 10 '17 at 19:30
  • Actually I just removed it out from the stackview, still didn't make a difference. Other methdods get hit, but not `cellforrowatindexpath`. FYI it's a tableView inside a UIView... – mfaani Oct 10 '17 at 19:31
  • @Honey not sure how to help with this amount of information, make sure to not return 0 in numberOfRows :) – Yaroslav Oct 10 '17 at 19:33
  • same issue i have use table view inside stack view not cellForRowAt indexPath not working – Harshil Kotecha Nov 16 '17 at 07:10
3

I solved the problem because my Subview on which i added the UITableView was not allocated so it was returning nill and tableview was not calling cellForRowAtIndexPath but numberOfRowsInSection getting called

Kuldeep
  • 4,466
  • 8
  • 32
  • 59
user1039695
  • 981
  • 11
  • 20
3

Wasted hours and then found out that my tableview was inside a stackview. As soon as I removed the stackview, it worked like a charm. For me it's Xcode 11.3 .

Tripti Kumar
  • 1,559
  • 14
  • 28
2

I am using a customer datasource and it was released. Because it is a weak reference of tableview.

TK189
  • 1,490
  • 1
  • 13
  • 17
2

This is embarrassing and perplexing, but here's my fix.

My code:

_scanResultTable.delegate = self;
_scanResultTable.dataSource = self; // self's lifecycle was fine, wasn't getting released
[_scanResultTable reloadData];

So the weird part is: the identifier _scanResultTable was never declared in my code, anywhere in the project. I have no idea how this compiled (and I recompiled several times).

My root cause was that I had linked my table output to scanResultTable in my ViewController, but was referring to it as _scanResultTable. Once I started using scanResultTable like I should've been, things cleared up. This makes me wonder if objective-c has something special about leading underscores in identifiers...

Edit: It does! Good lord I can't wait to never touch this language again.

ArtHare
  • 1,798
  • 20
  • 22
2

In my case, I had the TableView inside a StackView. When I put it outside the StackView it worked.

qwerty
  • 2,065
  • 2
  • 28
  • 39
el3ankaboot
  • 302
  • 2
  • 12
1

I'm using ReactiveCocoa. So I've created the model for table view. The data was prepared to be displayed, so numberOfRows and so on was called. But I haven't added the tableview as subview, thus cellForRowAtIndexPath was not called)))

Kuldeep
  • 4,466
  • 8
  • 32
  • 59
Naloiko Eugene
  • 2,453
  • 1
  • 28
  • 18
1
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    // Make sure self.data isn't nil!
    // If it is, you'll always return 0 and therefore
    // cellForRowAtIndexPath will never get called.
    return [self.data count]; 
}
John Erck
  • 9,478
  • 8
  • 61
  • 71
1

This maybe too obvious but in my case i was deleting an element (Label) which was a reference to the top margin of my table programmatically, so it was not a problem with delegate/source but of the table having some problems with the content-size / height, this conflict created a weird behaviour, calling numberOfRowsInSection but not cellForRowAt indexPath

Wilson Muñoz
  • 191
  • 5
  • 7
0

Try to insert new rows manually instead of [self.tableView reloadData]:

[self.tableView beginUpdates];
    for (int i = 0; i < responseArray.count; i++) {
        _rowsNumber += 1;
        NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
        [self.tableView insertRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationBottom];
    }
[self.tableView endUpdates];

In dataSource method return incremented int _rowsNumber:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return _rowsNumber;
}
Aleh
  • 386
  • 4
  • 8
0

i solved same issue by checking tableViewCell identifier. Go To Attributes İnspector and look at identifier section. Probably is missing. So write cell identifier.

Emre Gürses
  • 1,992
  • 1
  • 23
  • 28
0

Same case here, but it was a mistake:

I included a Scrollview with 2 tablesViews inside a ViewController (design requirements), in the scrollview controller I created the ViewControllers (that contains the TableViews) programatically and I used a weak var to store it, bad idea because they were released at the end of viewDidLoad method.

If you don't see your tableview content, please, check if it was released.

My mistake was very brain-painful for me because all methods (delegate&datasource) were called except viewForCell...

ucotta
  • 557
  • 4
  • 7
0

I have same issue i have use Table View inside the StackView and table view scroll disable and set height constrain but after that

tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell

Not working stop calling this method all Data Source and Delegate set properly.

if you have same issue than Solution is set Table View bottom , leading , trailing constrain

enter image description here

Harshil Kotecha
  • 2,846
  • 4
  • 27
  • 41
0

None of the answers here helped me.

I was using doing my constraints programmatically but forgot to write:

myTableView.translatesAutoresizingMaskIntoConstraints = false
mfaani
  • 33,269
  • 19
  • 164
  • 293
0

Make sure your constraints are correct and none of them is removed at build time.

rs7
  • 1,618
  • 1
  • 8
  • 16
0

Are you sure that after the user logged in and B is popped the viewWillAppear method in A gets called in order to perform the refresh?

If you show B as a modal controller, when you dismiss it you won't have the viewWillAppear method called.

As far as I know, viewWillAppear/viewDidAppear (and other like it) are events generated by the UINavigationController in the case of navigation events (push/pop viewcontrollers). So maybe that is why you eventually get your refresh when you leave A and return ... it all depends on the way to display the next viewcontroller.

Andrei Stanescu
  • 6,353
  • 4
  • 35
  • 64
  • Yes, I'm sure, I've put breakpoints in the code and I see they're hit. It all works fine up to the point where tableView:cellForRowAtIndexPath: should be called. – Stelian Iancu Oct 10 '11 at 13:36
  • Then i'm out of ideas. Maybe if you post some more code we can spot a possible mistake. But something is definitely fishy. – Andrei Stanescu Oct 10 '11 at 14:57