3

iPad: I have a table that behaves fine until the cells extend beyond the visible area (the height of the screen). When the last cell which partially off-screen is scrolled down so its no longer visible and then scrolled back up the app crashes? Why?

The code is repurposed from the iPhone screen which works fine(it's a universal app). Stumped by this one

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"JobCell";

    UITableViewCell *cell;
    NSArray *versionCompatibility = [[UIDevice currentDevice].systemVersion componentsSeparatedByString:@"."];

    if ( 6 <= [[versionCompatibility objectAtIndex:0] intValue] )
    {
        // iOS6 is installed
        cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
    }
    else
    {
        // iOS5 is installed
        cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier ];
    }


    // Configure the cell...
    Job *job = [self.jobs objectAtIndex:indexPath.row];

    NSLog(@"Dealing with cell %d",indexPath.row);

    UIColor *redcol = [UIColor colorWithRed:0.62745098039216 green:0.15294117647059 blue:0.15686274509804 alpha:1.0];

    NSString *jobString = job.jobAddress;
    jobString = [jobString stringByAppendingString:@", "];
    jobString = [jobString stringByAppendingString:job.jobPostcode];

    //cell.textLabel.text = jobString;

    NSLog(@"jobString %@",jobString);

    UILabel *jobLabel = (UILabel *)[cell.contentView viewWithTag:10];
    jobLabel.text = jobString;

    int jobsaved = job.jobSaved;

    if (jobsaved == 1)
    {
        //cell.textLabel.textColor = redcol;
        jobLabel.textColor = redcol;
    }
    else
    {
        //cell.textLabel.textColor = [UIColor blackColor];
        jobLabel.textColor = [UIColor blackColor];
    }

    NSLog(@"date %@",job.jobDate);

    UILabel *dateLabel = (UILabel *)[cell.contentView viewWithTag:11];

    if (! [job.jobDate isEqualToString:@""]) {


        NSDateFormatter *formater = [[NSDateFormatter alloc] init];
        [formater setDateFormat:@"yyyy-MM-dd"];

        NSDate *date2 = [formater dateFromString:job.jobDate];
        [formater setDateFormat:@"d MMM YYYY"];

        NSString *date = [formater stringFromDate:date2];

        //cell.detailTextLabel.text = date;
        dateLabel.text = date;

    }
    else
    {
        //cell.detailTextLabel.text = @"";
        dateLabel.text = @"";
    }

    UIButton *mapBtn = (UIButton *)[cell viewWithTag:12];
    [mapBtn addTarget:self action:@selector(showMap:) forControlEvents:UIControlEventTouchUpInside];
    mapBtn.tag = indexPath.row;

    return cell;
}

The error is

 -[UIButton setText:]: unrecognized selector sent to instance 0x2aba90
2013-07-04 09:32:48.563 ESC GasCert[3175:707] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UIButton setText:]: unrecognized selector sent to instance 0x2aba90'
*** First throw call stack:
(0x37e208bf 0x3796c1e5 0x37e23acb 0x37e22945 0x37d7d680 0x6e0e3 0x319289cb 0x31927aa9 0x31927233 0x318cbd4b 0x37d7f22b 0x37419381 0x37418f99 0x3741d11b 0x3741ce57 0x374446f1 0x374674c5 0x37467379 0x34b76f93 0x3764e891 0x37de9f43 0x37df4553 0x37df44f5 0x37df3343 0x37d764dd 0x37d763a5 0x37b4dfcd 0x318f6743 0x22f1 0x2278)
terminate called throwing an exception(lldb)

The crash

0x217a:  blx    0xa7ef8                   ; symbol stub for: NSStringFromClass
0x217e:  mov    r7, r7
0x2180:  blx    0xa7f8c                   ; symbol stub for: objc_retainAutoreleasedReturnValue
0x2184:  movs   r2, #0
0x2186:  movt   r2, #0
0x218a:  ldr    r1, [sp, #8]
0x218c:  str    r0, [sp]
0x218e:  mov    r0, r1
0x2190:  ldr    r1, [sp, #4]
0x2192:  ldr    r3, [sp]
0x2194:  blx    0xa7ed8                   ; symbol stub for: UIApplicationMain
0x2198:  str    r0, [sp, #32]

I've tried

.h

@property (nonatomic, copy) NSString *jobString;

.m

_jobString = job.jobAddress;
_jobString = [_jobString stringByAppendingString:@", "];
_jobString = [_jobString stringByAppendingString:job.jobPostcode];

Edit:

This is the code for the iPhone storyboard which does NOT crash

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"JobCell";

    UITableViewCell *cell;
    NSArray *versionCompatibility = [[UIDevice currentDevice].systemVersion componentsSeparatedByString:@"."];

    if ( 6 <= [[versionCompatibility objectAtIndex:0] intValue] )
    {
        // iOS6 is installed
        cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
    }
    else
    {
        // iOS5 is installed
        cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier ];
    }

    // Configure the cell...
    Job *job = [self.jobs objectAtIndex:indexPath.row];

    UIColor *redcol = [UIColor colorWithRed:0.62745098039216 green:0.15294117647059 blue:0.15686274509804 alpha:1.0];

    NSString *jobString = job.jobAddress;
    jobString = [jobString stringByAppendingString:@", "];
    jobString = [jobString stringByAppendingString:job.jobPostcode];    

    //cell.textLabel.text = jobString;

    UILabel *jobLabel = (UILabel *)[cell viewWithTag:10];
    jobLabel.text = jobString;

    int jobsaved = job.jobSaved;

    if (jobsaved == 1)
    {
        //cell.textLabel.textColor = redcol;
        jobLabel.textColor = redcol;
    }
    else
    {
        //cell.textLabel.textColor = [UIColor blackColor];
        jobLabel.textColor = [UIColor blackColor];
    }

    NSLog(@"date %@",job.jobDate);

    UILabel *dateLabel = (UILabel *)[cell viewWithTag:11];

    if (! [job.jobDate isEqualToString:@""]) {


        NSDateFormatter *formater = [[NSDateFormatter alloc] init];
        [formater setDateFormat:@"yyyy-MM-dd"];

        NSDate *date2 = [formater dateFromString:job.jobDate];
        [formater setDateFormat:@"d MMM YYYY"];

        NSString *date = [formater stringFromDate:date2];

        //cell.detailTextLabel.text = date;
        dateLabel.text = date;

    }
    else
    {
        //cell.detailTextLabel.text = @"";
        dateLabel.text = @"";
    }

    UIButton *mapBtn = (UIButton *)[cell viewWithTag:12];
    [mapBtn addTarget:self action:@selector(showMap:) forControlEvents:UIControlEventTouchUpInside];
    mapBtn.tag = indexPath.row;

    return cell;
}

EDIT:

Decided whole using tag methodology is fraught with issues. Solved problem by using solution in this post Detecting which UIButton was pressed in a UITableView

Community
  • 1
  • 1
JulianB
  • 1,686
  • 1
  • 19
  • 31
  • Rather than use tags to identify your UI elements, you're better off using custom cells and accessing the elements as properties of the cells instead. – Abizern Jul 04 '13 at 08:51

2 Answers2

8

I think you're messing the tags, here is what i see : mapBtn.tag = indexPath.row; you shouldn't rewrite the tag like that because the UIButton may be retrieved by the viewWithTag instead of the UILabel, and that's why you get [UIButton setText:].

So you should find another way to mark the action.

soryngod
  • 1,827
  • 1
  • 14
  • 13
  • Exactly! A reason why using tags is something to be done with care. – Abizern Jul 04 '13 at 08:50
  • Ok... but I use exactly the same code with the iphone storyboard and it works fine, scrolling and all? So why crash only on the iPad? – JulianB Jul 04 '13 at 08:52
  • Maybe because the indexPath doesn't go as far as 12 , you might have less elements in the list. – soryngod Jul 04 '13 at 08:54
  • Look at the documentation of UITableViewCell. It has UILabel *_textLabel and UILabel *_detailTextLabel in its interface. So you can use it like cell.textLabel.text = jobString or you can use detailTextLabel if you need. – Numeral Jul 04 '13 at 08:57
  • @soryngod - this is the tag for a uibutton in the uitablecell, not the index.path – JulianB Jul 04 '13 at 09:06
  • @soryngod - the tag for the uibutton is et in the storyboard, it's not being automatically assigned. As I said the sam ecode works for teh iphone storyboard – JulianB Jul 04 '13 at 09:11
  • You are doing it like this : UIButton *mapBtn = (UIButton *)[cell viewWithTag:12]; mapBtn.tag = indexPath.row; so you're mistaking. Assigning the UIButton's tag with that value it overrides the initial tag and when the cell is redrawn it will mess the initialization of the cell retrieving the button instead of the label. – soryngod Jul 04 '13 at 09:12
  • Ok... but why does it work on the iPhone? And is there another way I can identify which cell a button was pressed in? Thanks in advance – JulianB Jul 04 '13 at 09:14
  • Hey! @soryngod you're right (as I bet you knew you were) I commented out mapBtn.tag = indexPath.row; and no crash. But no it crashes when I press the button... any hints on how to identify which cell. I was using UIButton* b = (UIButton*) sender; int bTag = b.tag; – JulianB Jul 04 '13 at 09:17
  • I cause could be because you have less cells in the iPhone and the indexPath doesn't reach the tag value, or I'm not very sure , but it might be because you have a bigger height of the table in the iPhone and a lower row height and the cells aren't redrawn as much as in iPad. – soryngod Jul 04 '13 at 09:17
  • You could try tagging the superview instead of the button and you can get the tag like this : NSInteger tag = [[sender superview] tag]; Not absolutely sure it will work but you should try, so you should tag the cell instead of the button. – soryngod Jul 04 '13 at 09:20
  • @JulianB The button is in a view hierarchy. You should be able to get the reference to the UITableViewCell. With this you can use UITableView's indexPathForCell: method. And once you have the indexPath you know all you need to know. – Abizern Jul 04 '13 at 09:22
  • @soryngod yes I'll try that as a quick fix - otherwise a dynamic prototype cell with custom properties might be the way to go...? Thanks for the pointer... knowing why is half the battle – JulianB Jul 04 '13 at 09:39
  • You could use a custom cell but if you only have 1 button is enough with the superview thing, if you place on creating more actions you should move to a better point of view. – soryngod Jul 04 '13 at 09:45
  • @soryngod Thank you - cell.tag = indexPath.row; - then - NSInteger bTag = [[sender superview] tag]; - all good – JulianB Jul 05 '13 at 11:19
  • Glad that I could help you! – soryngod Jul 05 '13 at 11:20
  • Sigh... actually no! 'NSInvalidArgumentException', reason: '-[UITableViewCellContentView setText:]: when scrolling. Do I need the superview of the superview tag. ie ContentView -> cell.tag – JulianB Jul 05 '13 at 11:32
  • Actually if you add the objects to the cell you need the cell.tag.That means the objects wasn't found in the contentView, you can see what are the subviews of your cell & cell.contentView in a log. – soryngod Jul 05 '13 at 11:33
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/32944/discussion-between-julianb-and-soryngod) – JulianB Jul 05 '13 at 11:37
-2

For iOS 5 you use

cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

Note that this will return nil, since you haven't initialized any cells with the identifier CellIdentifier. While dequeueReusableCellWithIdentifier:forIndexPath: always returns a valid cell, for dequeueReusableCellWithIdentifier: you need to initialize the cell (in case there's nothing to dequeue).

So you're returning nil to - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath (on versions < iOS6), which causes crashes as far as I can remember.

Here's the documentation paragraph:

Return Value

A UITableViewCell object with the associated identifier or nil if no such object exists in the reusable-cell queue.

alex-i
  • 5,406
  • 2
  • 36
  • 56
  • 1
    Not true. If storyboards are being used cells are created as needed. And a hint that this is not the issue is that the crash happens on scrolling, not when the cell is first created. The correct answer has already been provided by soryngod – Abizern Jul 04 '13 at 09:03
  • @Abizern I didn't know it works like that on storyboards, thanks for the info. – alex-i Jul 04 '13 at 09:16