Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

iOS 7 UITextView link detection crash in UITableView

I have a custom UITableView cell set up in my UITableView like this:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

    static NSString *identifier = @"CELL_IDENTIFIER";

    SGCustomCell *cell = (SGCustomCell *)[tableView dequeueReusableCellWithIdentifier:identifier];
    if (!cell) cell = [[SGCustomCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];

    cell = [self customizedCell:cell withPost:[postsArray objectAtIndex:indexPath.row]];

    return cell;
}

I set up the cell like this (specifically setting the UITextView.text to nil - as noted in this answer):

descriptionLabel.text = nil;
descriptionLabel.text = post.postDescription;

descriptionLabel.frame = CGRectMake(leftMargin - 4, currentTitleLabel.frame.origin.y + currentTitleLabel.frame.size.height + 10, self.frame.size.width - topMargin * 3, 100);
[descriptionLabel sizeToFit];

The cells are 100% reusable and UITextView is inited like this (as you see, nothing special):

descriptionLabel = [[UITextView alloc] init];
descriptionLabel.font = [UIFont fontWithName:@"HelveticaNeue" size:11];
descriptionLabel.editable = NO;
descriptionLabel.scrollEnabled = NO;
descriptionLabel.dataDetectorTypes = UIDataDetectorTypeLink;
descriptionLabel.frame = CGRectMake(leftMargin, currentTitleLabel.frame.origin.y + currentTitleLabel.frame.size.height + 10, self.frame.size.width - topMargin * 3, 10);
[self addSubview:descriptionLabel];

But when the table has around 50 cells and when I scroll it fast I get the following crash:

Terminating app due to uncaught exception 'NSRangeException', reason: 'NSMutableRLEArray objectAtIndex:effectiveRange:: Out of bounds'

Which is absolutely ridiculous - I comment out this line - descriptionLabel.dataDetectorTypes = UIDataDetectorTypeLink; and the app stops crashing! I've spent hours trying to figure out what the problem was and now I simply get this.

Tested on iOS 7.0.3

like image 382
Sergey Grischyov Avatar asked Nov 12 '13 11:11

Sergey Grischyov


1 Answers

The crash happens when two cells with data type are being dequeued while using the same cell identifier. It seems to be a bug in iOS, but Apple may have good reasons to implement it this way. (memory wise)

And so the only 100% bullet proof solution is to provide a unique identifier for cells containing data types. This doesn't mean you will set a unique identifier to all cells in your table, of course, as it will eat up too much memory and your table scroll will be really slow.

You can use NSDataDetector to determine if a matched type was found on your text, and only then save the found object as the cell identifier, like so:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{

    NSString *row = [self.dataSource objectAtIndex:indexPath.row];
    static NSDataDetector *detector = nil;
    if (!detector)
    {
        NSError *error = NULL;
        detector = [[NSDataDetector alloc] initWithTypes:NSTextCheckingTypeLink | NSTextCheckingTypePhoneNumber error:&error];
    }

    NSTextCheckingResult *firstDataType = [detector firstMatchInString:row
                                                               options:0
                                                                 range:NSMakeRange(0, [row length])];
    NSString *dataTypeIdentifier = @"0";
    if (firstDataType)
    {
        if (firstDataType.resultType == NSTextCheckingTypeLink)
            dataTypeIdentifier = [(NSURL *)[firstDataType URL] absoluteString];
        else if (firstDataType.resultType == NSTextCheckingTypePhoneNumber)
            dataTypeIdentifier = [firstDataType phoneNumber];
    }

    NSString *CellIdentifier = [NSString stringWithFormat:@"Cell_%@", dataTypeIdentifier];

    UITableViewCell *cell = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
...

Note: Initializing NSDataDetector *detector as static rather than initialize it for each cell improves performance.

like image 100
AmitP Avatar answered Oct 08 '22 20:10

AmitP