IOS 7 UITextView обнаружение связи в UITableView

У меня есть пользовательская ячейка UITableView, настроенная в моем UITableView, как это:

- (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;
}

Я установил ячейку следующим образом (в частности, установив UITextView.text в nil - как отмечено в этом ответе):

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];

Ячейки 100% многоразовые, а UITextView используется так (как вы видите, ничего особенного):

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];

Но когда таблица имеет около 50 ячеек и когда я быстро прокручиваю, я получаю следующий сбой:

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

Что совершенно смешно - я прокомментирую эту строку - descriptionLabel.dataDetectorTypes = UIDataDetectorTypeLink;, и приложение перестает рушиться! Я потратил часы, пытаясь понять, в чем проблема, и теперь я просто получаю это.

Протестировано на iOS 7.0.3

Ответ 1

Сбой происходит, когда две ячейки с типом данных используя тот же идентификатор ячейки. Кажется, это ошибка в iOS, но у Apple могут быть веские причины для ее реализации. (память разумная)

И поэтому единственное 100% -ное доказательство пули - предоставить уникальный идентификатор для ячеек содержащих типы данных. Это не означает, что вы установите уникальный идентификатор для всех ячеек в вашей таблице, конечно, так как он будет потреблять слишком много памяти, а свиток таблицы будет очень медленным.

Вы можете использовать NSDataDetector, чтобы определить, был ли найден подходящий тип в вашем тексте, и только затем сохранить найденный объект в качестве идентификатора ячейки, например:

- (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];
...

Примечание. Инициализация детектора NSDataDetector * как статического а не инициализировать его для каждой ячейки, повышает производительность.

Ответ 2

Я мог бы воспроизвести ваш крах. Реализация следующего метода в подклассе TableViewCell

- (void)prepareForReuse
{
    [super prepareForReuse];
    [descriptionLabel setDataDetectorTypes: UIDataDetectorTypeNone];
}

и добавьте следующий вызов в пределах - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath перед установкой текста:

[descriptionLabel setDataDetectorTypes: UIDataDetectorTypeLink];

работал у меня. Возможно, он отменяет текущий чертеж внутри текстового поля и избегает аварии таким образом.

edit: Вызов [descriptionLabel setDataDetectorTypes: UIDataDetectorTypeNone]; и [descriptionLabel setDataDetectorTypes: UIDataDetectorTypeLink]; непосредственно перед установкой текста также, похоже, устраняет сбой

Ответ 3

При условии, что вы используете iOS6 или выше, вы можете использовать NSDataDetector для создания атрибутивной строки и использовать ее в качестве текста TextView. Модифицированная версия следующего метода - это то, что мы будем использовать. Метод принимает строку и некоторые уже предопределенные атрибуты (например, шрифт и цвет текста) и останавливается после 100-й ссылки. Однако у него есть несколько проблем с несколькими телефонными номерами. Вам нужно определить свой собственный код для URL-адреса, пропуская адрес. Бит NSDataDetector был взят из ссылки Apple NSDataDetector: https://developer.apple.com/librarY/mac/documentation/Foundation/Reference/NSDataDetector_Class/Reference/Reference.html

NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:string attributes:attributes];
__block NSUInteger count = 0;
if (!_dataDetector)
{
    NSError *error = nil;
    _dataDetector = [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypeAddress | NSTextCheckingTypePhoneNumber | NSTextCheckingTypeLink
                                                    error:&error];
}
[_dataDetector enumerateMatchesInString:string
                                options:0
                                  range:NSMakeRange(0, [string length])
                             usingBlock:^(NSTextCheckingResult *match, NSMatchingFlags flags, BOOL *stop){
                                 NSRange matchRange = [match range];
                                 if ([match resultType] == NSTextCheckingTypeLink)
                                 {
                                     NSURL *url = [match URL];
                                     if (url)
                                     {
                                         [attributedString addAttribute:NSLinkAttributeName value:url range:matchRange];
                                     }
                                 }
                                 else if ([match resultType] == NSTextCheckingTypePhoneNumber)
                                 {
                                     NSString *phoneNumber = [NSString stringWithFormat:@"tel:%@",[match phoneNumber]];
                                     NSURL *url = [NSURL URLWithString:phoneNumber];
                                     if (url)
                                     {
                                         [attributedString addAttribute:NSLinkAttributeName value:url range:matchRange];
                                     }
                                 }
                                 else if ([match resultType] == NSTextCheckingTypeAddress)
                                 {
                 //Warning! You must URL escape this!
                                     NSString *address = [string substringWithRange:matchRange];
                 //Warning! You must URL escape this!

                                     NSString *urlString = [NSString stringWithFormat:@"http://maps.apple.com/?q=%@",address];
                                     NSURL *url = [NSURL URLWithString:urlString];
                                     if (url)
                                     {
                                         [attributedString addAttribute:NSLinkAttributeName value:url range:matchRange];
                                     }
                                 }
                                 if (++count >= 100) *stop = YES;
                             }];
return attributedString;