3

Save my hair, please or point out my (obvious) error. I am trying to use a UITableViewStyle of UITableViewCellStyleSubtitle in a subclassed UITableViewController.

I define a static constant in the implementation:

static NSString * const kAHCellIdentifier;

In viewDidLoad I register a tableView class:

[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:kAHCellIdentifier];

Then in tableView:cellForRowAtIndexPath I initialise the the cell as follows:

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kAHCellIdentifier];
if (cell == nil) {
    cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kAHCellIdentifier];
}

When running the app (device or sim) the cells load but the detailTextLabel doesn't show, however if I initialise like this, it works correctly*

My guess is that the constant is not actually static, or that there is a bug (8.0.2), or there is something that I am missing completely due to lack of sleep or whatever.

It can also be noted, that if I don't register the class there is a compiler error (expected) but no error even though the cell identifiers are different (which I guess is expected as I could register different classes for different cell styles.

What am I missing?

Edit

Using [tableView dequeueReusableCellWithIdentifier:kAHCellIdentifier forIndexPath:indexPath]; has no effect either.

Shorter question

Why does the dequeueReusableCellWithIdentifier return nil and thus initiate the cell using the UITableViewCellStyle in the cell == nil block if I use a static NSString in the cellForRowAtIndexPath method call, and not, if I use a class level global constant as defined above.

Additional

Some more testing has yielded some different results. If you register a class for the cell reuse identifier and then give the cell a different identifier in cellForRowAtIndexPath then it works. If you give it the same name as the registered class, then it doesn't work as expected.

Answer

After some testing, and @MANIAK_dobrii's answer, some clarity.

If you want to use the newer cell dequeue method dequeueReusableCellWithIdentifier:forIndexPath and need to use a UITableViewCellStyle other than default, you'll need to subclass UITableViewCell and override the tableview style in the initWithStyle method call.

If you are happy to use the older method then make sure you don't register a class for the reuse identifier otherwise it will not work.

Byron Rode
  • 525
  • 5
  • 15

1 Answers1

3

That's interesting, because I've just found a new paragraph in the docs for dequeueReusableCellWithIdentifier: I didn't see last time I was looking there:

If you registered a class for the specified identifier and a new cell must be created, this method initializes the cell by calling its initWithStyle:reuseIdentifier: method. For nib-based cells, this method loads the cell object from the provided nib file. If an existing cell was available for reuse, this method calls the cell’s prepareForReuse method instead.

Does this mean that if you register a class even dequeueReusableCellWithIdentifier: will try to create cell if it is not available in the reuse queue and always return a valid cell? Since -dequeueReusableCellWithIdentifier:forIndexPath: got available, I was using only it. So, my guess is that your

if (cell == nil) {
    cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}

never get's called and you end up with cells created via

[[registred_class alloc] initWithStyle:STANDARD_STYLE reuseIdentifier:registred_reuse_identifier];

At the same time, you've stated, that you get compile time errors if you don't register class, that's strange, you should not get any.

So, my solution would be or:
1. Don't register cell class via -registerClass:forCellReuseIdentifier: and use -dequeueReusableCellWithIdentifier: to get reused cells or create them as you're doing it right now. Again, use oldschool approach, you don't register anything, just dequeue if there is and create if there is no.

// I've put it here just for a clarification that that's doesn't matter for the case
static NSString *const cellID = @"CellID";
// Just this, do not register anything, cell should get the same id as you ask
// i.e. you should init cell with the same id you then try to dequeue
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID];
    if(!cell)
    {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellID];
    }

    cell.textLabel.text = @"Title";
    cell.detailTextLabel.text = @"subtitle";

    return cell;
}

or:
2. Create nib, put a UITableViewCell there, make it's style be UITableViewCellStyleSubtitle and register that nib for a reuse identifier instead of a class with registerNib:forCellReuseIdentifier:.

Also, make sure, you set all the titles/colors/transparencies right, so that you don't just set empty string or nil to a label on a cell.

MANIAK_dobrii
  • 6,014
  • 3
  • 28
  • 55
  • The dequeued cell never returns nil, so as you correctly assumed, the `cell == nil` block is never called. What I don't seem to understand is why if I use a static NSString cell identifier called within the `cellForRowAtIndexPath` call works, versus using the same dequeue call with a class level global constant (updated question above). – Byron Rode Oct 20 '14 at 11:30
  • @ByronRode Not sure what could cause this (i.e. move declaration to the file level and it doesn't work, besides, how? what changes? it crashes or what?). Maybe there's something stupid, like typo, different variable or forgotten line that breaks it all (usually there's no magic and the issue is something like that). Or maybe that file level declaration line is in different file and there's some compiler configuration error. So, from the outer point of view there should be nothing bad if you just use oldschool ~deqCellWithId: with global cellID.You could start from scratch and see if it works. – MANIAK_dobrii Oct 20 '14 at 11:49
  • see my additional edit above. It doesn't work if the reuseIdentifier in the class and cell are the same (weird, right) but does if the cell and class are different. – Byron Rode Oct 20 '14 at 11:53
  • Why do you even need to register class if you use old dequeueReusableCellWithIdentifier: ? Registreing class (or nib) is required for dequeueReusableCellWithIdentifier:atIndexPath: so it could always return a valid cell creating it either with registred class or from registred nib. The problem is that you register a class, while you don't need to. – MANIAK_dobrii Oct 20 '14 at 11:56
  • I wasn't initially using the old way, but the newer version with which is where the issue is, but my guess is it is a bug. – Byron Rode Oct 20 '14 at 11:59
  • @ByronRode, you have bug in your code. Just made up a test project. Managed to achieve what you want in 2 minutes. I've updated my answer with code sample. – MANIAK_dobrii Oct 20 '14 at 12:00
  • Your answer is correct using the old method. The key is not registering the class if you use the old method. If you use the new method, then you need to subclass the cell to override the initialise call to use the table style (if you don't want to use the default). – Byron Rode Oct 20 '14 at 12:14
  • 1
    @ByronRode if you don't want to subclass anything there's still a second solution I proposed which could be finely used with the new way. But I'd suggest using the old way, don't see much profit if you have to do so much (subclass, register) if you just can add two-three lines to create a cell if there's no reusable cells in the queue. – MANIAK_dobrii Oct 20 '14 at 12:23
  • I've updated my question with a partial solution, but yours definitely helped identify, so I have given you the points and marked your answer as correct. – Byron Rode Oct 20 '14 at 12:28